diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceType.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceType.java index 66120c0b14..f1a1480859 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceType.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceType.java @@ -47,4 +47,8 @@ public class ASTClassOrInterfaceType extends AbstractJavaTypeNode { } return false; } + + public boolean isAnonymousClass() { + return jjtGetParent().hasDescendantOfType(ASTClassOrInterfaceBody.class); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeArgument.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeArgument.java index e9139d14cc..7b0ffb8ffd 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeArgument.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeArgument.java @@ -5,7 +5,7 @@ package net.sourceforge.pmd.lang.java.ast; -public class ASTTypeArgument extends AbstractJavaNode { +public class ASTTypeArgument extends AbstractJavaTypeNode { public ASTTypeArgument(int id) { super(id); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeBound.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeBound.java index 4cfba7ab58..3c0a5bf845 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeBound.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeBound.java @@ -5,7 +5,7 @@ package net.sourceforge.pmd.lang.java.ast; -public class ASTTypeBound extends AbstractJavaNode { +public class ASTTypeBound extends AbstractJavaTypeNode { public ASTTypeBound(int id) { super(id); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeParameter.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeParameter.java index 4c412b7adc..6cd0ba9b37 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeParameter.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeParameter.java @@ -5,7 +5,7 @@ package net.sourceforge.pmd.lang.java.ast; -public class ASTTypeParameter extends AbstractJavaNode { +public class ASTTypeParameter extends AbstractJavaTypeNode { public ASTTypeParameter(int id) { super(id); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaAccessTypeNode.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaAccessTypeNode.java index cb19cbbc36..19b5d9f7f5 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaAccessTypeNode.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaAccessTypeNode.java @@ -4,9 +4,10 @@ package net.sourceforge.pmd.lang.java.ast; -public abstract class AbstractJavaAccessTypeNode extends AbstractJavaAccessNode implements TypeNode { +import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition; - private Class type; +public abstract class AbstractJavaAccessTypeNode extends AbstractJavaAccessNode implements TypeNode { + private JavaTypeDefinition typeDefinition; public AbstractJavaAccessTypeNode(int i) { super(i); @@ -18,11 +19,25 @@ public abstract class AbstractJavaAccessTypeNode extends AbstractJavaAccessNode @Override public Class getType() { - return type; + if (typeDefinition != null) { + return typeDefinition.getType(); + } + + return null; } @Override public void setType(Class type) { - this.type = type; + typeDefinition = JavaTypeDefinition.build(type); + } + + @Override + public JavaTypeDefinition getTypeDefinition() { + return typeDefinition; + } + + @Override + public void setTypeDefinition(JavaTypeDefinition typeDefinition) { + this.typeDefinition = typeDefinition; } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaTypeNode.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaTypeNode.java index 039780fcf6..6f0a9dee64 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaTypeNode.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaTypeNode.java @@ -4,15 +4,16 @@ package net.sourceforge.pmd.lang.java.ast; +import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition; + /** * An extension of the SimpleJavaNode which implements the TypeNode interface. - * + * * @see AbstractJavaNode * @see TypeNode */ public abstract class AbstractJavaTypeNode extends AbstractJavaNode implements TypeNode { - - private Class type; + private JavaTypeDefinition typeDefinition; public AbstractJavaTypeNode(int i) { super(i); @@ -24,11 +25,25 @@ public abstract class AbstractJavaTypeNode extends AbstractJavaNode implements T @Override public Class getType() { - return type; + if (typeDefinition != null) { + return typeDefinition.getType(); + } + + return null; } @Override public void setType(Class type) { - this.type = type; + typeDefinition = JavaTypeDefinition.build(type); + } + + @Override + public JavaTypeDefinition getTypeDefinition() { + return typeDefinition; + } + + @Override + public void setTypeDefinition(JavaTypeDefinition typeDefinition) { + this.typeDefinition = typeDefinition; } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/TypeNode.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/TypeNode.java index 5ed05bb2a4..91a2518be5 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/TypeNode.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/TypeNode.java @@ -5,6 +5,7 @@ package net.sourceforge.pmd.lang.java.ast; import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition; /** * This interface allows a Java Class to be associated with a node. @@ -13,16 +14,31 @@ public interface TypeNode extends Node { /** * Get the Java Class associated with this node. - * + * * @return The Java Class, may return null. */ Class getType(); + /** + * Get the TypeDefinition associated with this node. The Class object + * contained in the TypeDefinition will always be equal to that which + * is returned by getType(). + * + * @return The TypeDefinition, may return null + */ + JavaTypeDefinition getTypeDefinition(); + + /** + * Set the TypeDefinition associated with this node. + * + * @param type A TypeDefinition object + */ + void setTypeDefinition(JavaTypeDefinition type); + /** * Set the Java Class associated with this node. - * - * @param type - * A Java Class + * + * @param type A Java Class */ void setType(Class type); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/imports/UnusedImportsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/imports/UnusedImportsRule.java index ceb81ef077..419f8962c8 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/imports/UnusedImportsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/imports/UnusedImportsRule.java @@ -86,7 +86,14 @@ public class UnusedImportsRule extends AbstractJavaRule { if (s != null) { String[] params = s.split("\\s*,\\s*"); for (String param : params) { - imports.remove(new ImportWrapper(param, param, new DummyJavaNode(-1))); + final int firstDot = param.indexOf('.'); + final String expectedImportName; + if (firstDot == -1) { + expectedImportName = param; + } else { + expectedImportName = param.substring(0, firstDot); + } + imports.remove(new ImportWrapper(param, expectedImportName, new DummyJavaNode(-1))); } } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/junit/JUnitTestsShouldIncludeAssertRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/junit/JUnitTestsShouldIncludeAssertRule.java index eb20a28299..fd9678d0ac 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/junit/JUnitTestsShouldIncludeAssertRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/junit/JUnitTestsShouldIncludeAssertRule.java @@ -9,7 +9,6 @@ import java.util.List; import java.util.Map; import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTAnnotation; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMarkerAnnotation; @@ -81,7 +80,7 @@ public class JUnitTestsShouldIncludeAssertRule extends AbstractJUnitRule { for (NameDeclaration decl : decls.keySet()) { Node parent = decl.getNode().jjtGetParent().jjtGetParent().jjtGetParent(); - if (parent.hasDescendantOfType(ASTAnnotation.class) + if (parent.hasDescendantOfType(ASTMarkerAnnotation.class) && parent.getFirstChildOfType(ASTFieldDeclaration.class) != null) { String annot = parent.getFirstDescendantOfType(ASTMarkerAnnotation.class).jjtGetChild(0).getImage(); if (!"Rule".equals(annot) && !"org.junit.Rule".equals(annot)) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/logging/InvalidSlf4jMessageFormatRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/logging/InvalidSlf4jMessageFormatRule.java index b99abf17ff..4eb61ffcdd 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/logging/InvalidSlf4jMessageFormatRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/logging/InvalidSlf4jMessageFormatRule.java @@ -70,23 +70,9 @@ public class InvalidSlf4jMessageFormatRule extends AbstractJavaRule { // find the arguments final List argumentList = parentNode.getFirstChildOfType(ASTPrimarySuffix.class) .getFirstDescendantOfType(ASTArgumentList.class).findChildrenOfType(ASTExpression.class); - final List params = new ArrayList(argumentList.size()); - for (final ASTExpression astExpression : argumentList) { - ASTPrimaryExpression primaryExpression = astExpression.getFirstChildOfType(ASTPrimaryExpression.class); - if (primaryExpression != null) { - params.add(primaryExpression); - } - } - - if (params.isEmpty()) { - // no params we could analyze - return super.visit(node, data); - } - - final ASTPrimaryExpression messageParam = params.get(0); // remove the message parameter - params.remove(0); + final ASTPrimaryExpression messageParam = argumentList.remove(0).getFirstDescendantOfType(ASTPrimaryExpression.class); final int expectedArguments = expectedArguments(messageParam); if (expectedArguments == 0) { @@ -97,14 +83,14 @@ public class InvalidSlf4jMessageFormatRule extends AbstractJavaRule { // Remove throwable param, since it is shown separately. // But only, if it is not used as a placeholder argument - if (params.size() > expectedArguments) { - removeThrowableParam(params); + if (argumentList.size() > expectedArguments) { + removeThrowableParam(argumentList); } - if (params.size() < expectedArguments) { - addViolationWithMessage(data, node, "Missing arguments," + getExpectedMessage(params, expectedArguments)); - } else if (params.size() > expectedArguments) { - addViolationWithMessage(data, node, "Too many arguments," + getExpectedMessage(params, expectedArguments)); + if (argumentList.size() < expectedArguments) { + addViolationWithMessage(data, node, "Missing arguments," + getExpectedMessage(argumentList, expectedArguments)); + } else if (argumentList.size() > expectedArguments) { + addViolationWithMessage(data, node, "Too many arguments," + getExpectedMessage(argumentList, expectedArguments)); } return super.visit(node, data); @@ -146,21 +132,20 @@ public class InvalidSlf4jMessageFormatRule extends AbstractJavaRule { return false; } - private void removeThrowableParam(final List params) { + private void removeThrowableParam(final List params) { // Throwable parameters are the last one in the list, if any. if (params.isEmpty()) { return; } int lastIndex = params.size() - 1; - ASTPrimaryExpression last = params.get(lastIndex); + ASTPrimaryExpression last = params.get(lastIndex).getFirstDescendantOfType(ASTPrimaryExpression.class); if (isNewThrowable(last) || hasTypeThrowable(last) || isReferencingThrowable(last)) { params.remove(lastIndex); - return; } } - private String getExpectedMessage(final List params, final int expectedArguments) { + private String getExpectedMessage(final List params, final int expectedArguments) { return " expected " + expectedArguments + (expectedArguments > 1 ? " arguments " : " argument ") + "but have " + params.size(); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strictexception/SignatureDeclareThrowsExceptionRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strictexception/SignatureDeclareThrowsExceptionRule.java index 0c36c5a87f..8b22268cce 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strictexception/SignatureDeclareThrowsExceptionRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strictexception/SignatureDeclareThrowsExceptionRule.java @@ -8,6 +8,7 @@ import java.util.Collections; import java.util.List; import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ast.ASTAnnotation; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration; @@ -51,6 +52,15 @@ public class SignatureDeclareThrowsExceptionRule extends AbstractJavaRule { if (methodDeclaration.getMethodName().startsWith("test")) { return super.visit(methodDeclaration, o); } + + // Ignore overridden methods, the issue should be marked on the method definition + final List methodAnnotations = methodDeclaration.jjtGetParent().findChildrenOfType(ASTAnnotation.class); + for (final ASTAnnotation annotation : methodAnnotations) { + final ASTName annotationName = annotation.getFirstDescendantOfType(ASTName.class); + if (annotationName.hasImageEqualTo("Override") || annotationName.hasImageEqualTo("java.lang.Override")) { + return super.visit(methodDeclaration, o); + } + } List exceptionList = Collections.emptyList(); ASTNameList nameList = methodDeclaration.getFirstChildOfType(ASTNameList.class); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java index fe4ab26274..eee3bbb85f 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java @@ -6,6 +6,10 @@ package net.sourceforge.pmd.lang.java.typeresolution; import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -20,26 +24,29 @@ import net.sourceforge.pmd.lang.java.ast.ASTAdditiveExpression; import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression; import net.sourceforge.pmd.lang.java.ast.ASTAndExpression; import net.sourceforge.pmd.lang.java.ast.ASTAnnotationTypeDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTArguments; import net.sourceforge.pmd.lang.java.ast.ASTArrayDimsAndInits; import net.sourceforge.pmd.lang.java.ast.ASTBooleanLiteral; import net.sourceforge.pmd.lang.java.ast.ASTCastExpression; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBody; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTConditionalAndExpression; import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression; import net.sourceforge.pmd.lang.java.ast.ASTConditionalOrExpression; +import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTEqualityExpression; import net.sourceforge.pmd.lang.java.ast.ASTExclusiveOrExpression; import net.sourceforge.pmd.lang.java.ast.ASTExpression; +import net.sourceforge.pmd.lang.java.ast.ASTExtendsList; import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTInclusiveOrExpression; import net.sourceforge.pmd.lang.java.ast.ASTInstanceOfExpression; import net.sourceforge.pmd.lang.java.ast.ASTLiteral; import net.sourceforge.pmd.lang.java.ast.ASTMarkerAnnotation; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMultiplicativeExpression; import net.sourceforge.pmd.lang.java.ast.ASTName; import net.sourceforge.pmd.lang.java.ast.ASTNormalAnnotation; @@ -50,7 +57,6 @@ import net.sourceforge.pmd.lang.java.ast.ASTPreDecrementExpression; import net.sourceforge.pmd.lang.java.ast.ASTPreIncrementExpression; import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression; import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix; -import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix; import net.sourceforge.pmd.lang.java.ast.ASTPrimitiveType; import net.sourceforge.pmd.lang.java.ast.ASTReferenceType; import net.sourceforge.pmd.lang.java.ast.ASTRelationalExpression; @@ -58,16 +64,24 @@ import net.sourceforge.pmd.lang.java.ast.ASTShiftExpression; import net.sourceforge.pmd.lang.java.ast.ASTSingleMemberAnnotation; import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression; import net.sourceforge.pmd.lang.java.ast.ASTType; +import net.sourceforge.pmd.lang.java.ast.ASTTypeArgument; +import net.sourceforge.pmd.lang.java.ast.ASTTypeArguments; +import net.sourceforge.pmd.lang.java.ast.ASTTypeBound; import net.sourceforge.pmd.lang.java.ast.ASTTypeDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTTypeParameter; +import net.sourceforge.pmd.lang.java.ast.ASTTypeParameters; import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpression; import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpressionNotPlusMinus; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.ast.AbstractJavaTypeNode; import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorAdapter; +import net.sourceforge.pmd.lang.java.ast.Token; import net.sourceforge.pmd.lang.java.ast.TypeNode; import net.sourceforge.pmd.lang.java.symboltable.ClassScope; import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; +import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition; +import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinitionBuilder; import net.sourceforge.pmd.lang.symboltable.NameOccurrence; import net.sourceforge.pmd.lang.symboltable.Scope; @@ -142,6 +156,12 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter { private List importedOnDemand; private int anonymousClassCounter = 0; + /** + * Contains Class -> JavaTypeDefinitions map for raw Class types. Also helps to avoid infinite recursion + * when determining default upper bounds. + */ + private Map, JavaTypeDefinition> classToDefaultUpperBounds = new HashMap<>(); + public ClassTypeResolver() { this(ClassTypeResolver.class.getClassLoader()); } @@ -213,8 +233,11 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter { @Override public Object visit(ASTClassOrInterfaceType node, Object data) { + super.visit(node, data); + String typeName = node.getImage(); - if (node.jjtGetParent().hasDescendantOfType(ASTClassOrInterfaceBody.class)) { + + if (node.isAnonymousClass()) { anonymousClassCounter++; AbstractNode parent = node.getFirstParentOfType(ASTClassOrInterfaceDeclaration.class); if (parent == null) { @@ -222,7 +245,23 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter { } typeName = parent.getImage() + "$" + anonymousClassCounter; } + populateType(node, typeName); + + ASTTypeArguments typeArguments = node.getFirstChildOfType(ASTTypeArguments.class); + + if (typeArguments != null) { + JavaTypeDefinitionBuilder builder = JavaTypeDefinition.builder(node.getType()); + + for (int index = 0; index < typeArguments.jjtGetNumChildren(); ++index) { + builder.addTypeArg(((TypeNode) typeArguments.jjtGetChild(index)).getTypeDefinition()); + } + + node.setTypeDefinition(builder.build()); + } else if (isGeneric(node.getType()) && node.getTypeDefinition().getGenericArgs().size() == 0) { + node.setTypeDefinition(getDefaultUpperBounds(null, node.getType())); + } + return data; } @@ -246,78 +285,126 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter { @Override public Object visit(ASTName node, Object data) { - /* - * Only doing this for nodes where getNameDeclaration is null this means - * it's not a named node, i.e. Static reference or Annotation Doing this - * for memory - TODO: Investigate if there is a valid memory concern or - * not - */ - - Class accessingClass = getEnclosingTypeDeclaration(node); + Class accessingClass = getEnclosingTypeDeclaration(node); String[] dotSplitImage = node.getImage().split("\\."); - Class previousNameType = getVariableNameType(node.getScope(), dotSplitImage[0], accessingClass); + JavaTypeDefinition previousType + = getTypeDefinitionOfVariableFromScope(node.getScope(), dotSplitImage[0], accessingClass); - if (node.getNameDeclaration() != null && previousNameType == null) { + + if (node.getNameDeclaration() != null + && previousType == null // if it's not null, then let other code handle things + && node.getNameDeclaration().getNode() instanceof TypeNode) { // Carry over the type from the declaration - if (node.getNameDeclaration().getNode() instanceof TypeNode) { - node.setType(((TypeNode) node.getNameDeclaration().getNode()).getType()); + Class nodeType = ((TypeNode) node.getNameDeclaration().getNode()).getType(); + // generic classes and class with generic super types could have the wrong type assigned here + if (nodeType != null && !isGeneric(nodeType) && !isGeneric(nodeType.getSuperclass())) { + node.setType(nodeType); } } - if (node.getType() == null) { + // TODO: handle cases where static fields are accessed in a fully qualified way + // make sure it handles same name classes and packages + // TODO: handle generic static field cases + // handles cases where first part is a fully qualified name populateType(node, node.getImage()); if (node.getType() == null) { - populateType(node, dotSplitImage[0]); - } - - if (node.getType() == null) { - for (int i = 1; i < dotSplitImage.length; ++i) { - if (previousNameType == null) { + if (previousType == null) { break; } - Field field = getFirstVisibleFieldFromClass(previousNameType, dotSplitImage[i], accessingClass); - - if (field != null) { - previousNameType = field.getType(); - } else { - previousNameType = null; - } + previousType = getFieldType(previousType, dotSplitImage[i], accessingClass); } - node.setType(previousNameType); + if (previousType != null) { + node.setTypeDefinition(previousType); + } } } return super.visit(node, data); } - private Class getVariableNameType(Scope scope, String image, Class accessingClass) { + /** + * Searches a JavaTypeDefinition and it's superclasses until a field with name {@code fieldImage} that + * is visible from the {@code accessingClass} class. Once it's found, it's possibly generic type is + * resolved with the help of {@code typeToSearch} TypeDefinition. + * + * @param typeToSearch The type def. to search the field in. + * @param fieldImage The simple name of the field. + * @param accessingClass The class that is trying to access the field, some Class declared in the current ACU. + * @return JavaTypeDefinition of the resolved field or null if it could not be found. + */ + private JavaTypeDefinition getFieldType(JavaTypeDefinition typeToSearch, String fieldImage, Class + accessingClass) { + while (typeToSearch != null) { + try { + Field field = typeToSearch.getType().getDeclaredField(fieldImage); + if (isMemberVisibleFromClass(typeToSearch.getType(), field.getModifiers(), accessingClass)) { + return getNextTypeDefinition(typeToSearch, field.getGenericType()); + } + } catch (NoSuchFieldException e) { /* swallow */ } + + // transform the type into it's supertype + typeToSearch = getNextTypeDefinition(typeToSearch, typeToSearch.getType().getGenericSuperclass()); + } + + return null; + } + + /** + * Search for a field by it's image stating from a scope and taking into account if it's visible from the + * accessingClass Class. The method takes into account that Nested inherited fields shadow outer scope fields. + * + * @param scope The scope to start the search from. + * @param image The name of the field, local variable or method parameter. + * @param accessingClass The Class (which is defined in the current ACU) that is trying to access the field. + * @return Type def. of the field, or null if it could not be resolved. + */ + private JavaTypeDefinition getTypeDefinitionOfVariableFromScope(Scope scope, String image, Class + accessingClass) { if (accessingClass == null) { return null; } for (/* empty */; scope != null; scope = scope.getParent()) { + // search each enclosing scope one by one for (Map.Entry> entry : scope.getDeclarations(VariableNameDeclaration.class).entrySet()) { if (entry.getKey().getImage().equals(image)) { - return entry.getKey().getType(); + ASTType typeNode = entry.getKey().getDeclaratorId().getTypeNode(); + + if (typeNode == null) { + // TODO : Type is infered, ie, this is a lambda such as (var) -> var.equals(other) + return null; + } + + if (typeNode.jjtGetChild(0) instanceof ASTReferenceType) { + return ((TypeNode) typeNode.jjtGetChild(0)).getTypeDefinition(); + } else { // primitive type + return JavaTypeDefinition.build(typeNode.getType()); + } } } // Nested class' inherited fields shadow enclosing variables if (scope instanceof ClassScope) { try { - Field inheritedField = getFirstVisibleFieldFromClass( - ((ClassScope) scope).getClassDeclaration().getType(), image, accessingClass); + // get the superclass type def. ot the Class the ClassScope belongs to + JavaTypeDefinition superClass + = getSuperClassTypeDefinition(((ClassScope) scope).getClassDeclaration().getNode(), + null); + // TODO: check if anonymous classes are class scope - if (inheritedField != null) { - return inheritedField.getType(); + // try searching this type def. + JavaTypeDefinition foundTypeDef = getFieldType(superClass, image, accessingClass); + + if (foundTypeDef != null) { // if null, then it's not an inherited field + return foundTypeDef; } } catch (ClassCastException e) { // if there is an anonymous class, getClassDeclaration().getType() will throw @@ -329,6 +416,164 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter { return null; } + /** + * Given a type def. and a Type, resolves the type into a JavaTypeDefinition. Takes into account + * simple Classes, TypeVariables, ParameterizedTypes and WildCards types. Can resolve nested Generic + * type arguments. + * + * @param context The JavaTypeDefinition in which the {@code genericType} was declared. + * @param genericType The Type to resolve. + * @return JavaTypeDefinition of the {@code genericType}. + */ + private JavaTypeDefinition getNextTypeDefinition(JavaTypeDefinition context, Type genericType) { + if (genericType == null) { + return null; + } + + if (genericType instanceof Class) { // Raw types take this branch as well + return getDefaultUpperBounds(context, (Class) genericType); + } else if (genericType instanceof ParameterizedType) { + + ParameterizedType parameterizedType = (ParameterizedType) genericType; + JavaTypeDefinitionBuilder typeDef = JavaTypeDefinition.builder((Class) parameterizedType.getRawType()); + + // recursively determine each type argument's type def. + for (Type type : parameterizedType.getActualTypeArguments()) { + typeDef.addTypeArg(getNextTypeDefinition(context, type)); + } + + return typeDef.build(); + } else if (genericType instanceof TypeVariable) { + int ordinal = getTypeParameterOrdinal(context.getType(), ((TypeVariable) genericType).getName()); + if (ordinal != -1) { + return context.getGenericArgs().get(ordinal); + } + } else if (genericType instanceof WildcardType) { + Type[] wildcardUpperBounds = ((WildcardType) genericType).getUpperBounds(); + if (wildcardUpperBounds.length != 0) { // upper bound wildcard + return getNextTypeDefinition(context, wildcardUpperBounds[0]); + } else { // lower bound wildcard + return JavaTypeDefinition.build(Object.class); + } + } + + return null; + } + + /** + * Returns the ordinal of the type parameter with the name {@code parameterName} in {@code clazz}. + * + * @param clazz The Class with the type parameters. + * @param parameterName The name of the type parameter. + * @return The ordinal of the type parameter. + */ + private int getTypeParameterOrdinal(Class clazz, String parameterName) { + TypeVariable[] classTypeParameters = clazz.getTypeParameters(); + + for (int index = 0; index < classTypeParameters.length; ++index) { + if (classTypeParameters[index].getName().equals(parameterName)) { + return index; + } + } + + return -1; + } + + /** + * Returns true if the class is generic. + * + * @param clazz The Class to examine. + * @return True if the Class is generic. + */ + private boolean isGeneric(Class clazz) { + if (clazz != null) { + return clazz.getTypeParameters().length != 0; + } + + return false; + } + + /** + * Given a Class, returns the type def. for when the Class stands without type arguments, meaning it + * is a raw type. Determines the generic types by looking at the upper bounds of it's generic parameters. + * + * @param context Synthetic parameter for recursion, pass {@code null}. + * @param clazzWithDefBounds The raw Class type. + * @return The type def. of the raw Class. + */ + private JavaTypeDefinition getDefaultUpperBounds(JavaTypeDefinition context, Class clazzWithDefBounds) { + JavaTypeDefinitionBuilder typeDef = JavaTypeDefinition.builder(clazzWithDefBounds); + + // helps avoid infinite recursion with Something<.... E extends Something (<- same raw type)... > + if (classToDefaultUpperBounds.containsKey(clazzWithDefBounds)) { + return classToDefaultUpperBounds.get(clazzWithDefBounds); + } else { + classToDefaultUpperBounds.put(clazzWithDefBounds, typeDef.build()); + } + + if (isGeneric(clazzWithDefBounds)) { + // Recursion, outer call should pass in null. + // Recursive calls will get the first JavaTypeDefinition to be able to resolve cases like + // ... < T extends Something ... E extends Other ... > + if (context == null) { + context = typeDef.build(); + } + + for (TypeVariable parameter : clazzWithDefBounds.getTypeParameters()) { + // TODO: fix self reference "< ... E extends Something ... >" + typeDef.addTypeArg(getNextTypeDefinition(context, parameter.getBounds()[0])); + } + } + + return typeDef.build(); + } + + /** + * Given a class, the modifiers of on of it's member and the class that is trying to access that member, + * returns true is the member is accessible from the accessingClass Class. + * + * @param classWithMember The Class with the member. + * @param modifiers The modifiers of that member. + * @param accessingClass The Class trying to access the member. + * @return True if the member is visible from the accessingClass Class. + */ + private boolean isMemberVisibleFromClass(Class classWithMember, int modifiers, Class accessingClass) { + if (accessingClass == null) { + return false; + } + + // public members + if (Modifier.isPublic(modifiers)) { + return true; + } + + boolean areInTheSamePackage; + if (accessingClass.getPackage() != null) { + areInTheSamePackage = accessingClass.getPackage().getName().startsWith( + classWithMember.getPackage().getName()); + } else { + return false; // if the package information is null, we can't do nothin' + } + + // protected members + if (Modifier.isProtected(modifiers)) { + if (areInTheSamePackage || classWithMember.isAssignableFrom(accessingClass)) { + return true; + } + // private members + } else if (Modifier.isPrivate(modifiers)) { + if (classWithMember.equals(accessingClass)) { + return true; + } + // package private members + } else if (areInTheSamePackage) { + return true; + } + + return false; + } + + @Override public Object visit(ASTFieldDeclaration node, Object data) { super.visit(node, data); @@ -512,14 +757,14 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter { return data; } + @Override public Object visit(ASTPrimaryExpression primaryNode, Object data) { super.visit(primaryNode, data); - Class primaryNodeType = null; + JavaTypeDefinition primaryNodeType = null; AbstractJavaTypeNode previousChild = null; - - Class accessingClass = getEnclosingTypeDeclaration(primaryNode); + Class accessingClass = getEnclosingTypeDeclaration(primaryNode); for (int childIndex = 0; childIndex < primaryNode.jjtGetNumChildren(); ++childIndex) { AbstractJavaTypeNode currentChild = (AbstractJavaTypeNode) primaryNode.jjtGetChild(childIndex); @@ -528,39 +773,43 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter { if (currentChild.getType() == null) { // Last token, because if 'this' is a Suffix, it'll have tokens '.' and 'this' if (currentChild.jjtGetLastToken().toString().equals("this")) { + if (previousChild != null) { // Qualified 'this' expression currentChild.setType(previousChild.getType()); } else { // simple 'this' expression ASTClassOrInterfaceDeclaration typeDeclaration = currentChild.getFirstParentOfType(ASTClassOrInterfaceDeclaration.class); + if (typeDeclaration != null) { - currentChild.setType(typeDeclaration.getType()); + currentChild.setTypeDefinition(typeDeclaration.getTypeDefinition()); } } // Last token, because if 'super' is a Suffix, it'll have tokens '.' and 'super' } else if (currentChild.jjtGetLastToken().toString().equals("super")) { + if (previousChild != null) { // Qualified 'super' expression - currentChild.setType(previousChild.getType().getSuperclass()); + // anonymous classes can't have qualified super expression, thus + // getSuperClassTypeDefinition's second argumet isn't null, but we are not + // looking for enclosing super types + currentChild.setTypeDefinition( + getSuperClassTypeDefinition(currentChild, previousChild.getType())); } else { // simple 'super' expression - ASTClassOrInterfaceDeclaration typeDeclaration - = currentChild.getFirstParentOfType(ASTClassOrInterfaceDeclaration.class); - if (typeDeclaration != null && typeDeclaration.getType() != null) { - currentChild.setType(typeDeclaration.getType().getSuperclass()); - } + currentChild.setTypeDefinition(getSuperClassTypeDefinition(currentChild, null)); } + } else if (previousChild != null && previousChild.getType() != null && currentChild.getImage() != null) { - Field field = getFirstVisibleFieldFromClass(previousChild.getType(), currentChild.getImage(), - accessingClass); - if (field != null) { - currentChild.setType(field.getType()); - } + + currentChild.setTypeDefinition(getFieldType(previousChild.getTypeDefinition(), + currentChild.getImage(), + accessingClass)); } } + if (currentChild.getType() != null) { - primaryNodeType = currentChild.getType(); + primaryNodeType = currentChild.getTypeDefinition(); } else { // avoid falsely passing tests primaryNodeType = null; @@ -570,79 +819,76 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter { previousChild = currentChild; } - primaryNode.setType(primaryNodeType); + primaryNode.setTypeDefinition(primaryNodeType); return data; } - private Class getEnclosingTypeDeclaration(Node node) { + /** + * Returns the type def. of the first Class declaration around the node. Looks for Class declarations + * and if the second argument is null, then for anonymous classes as well. + * + * @param node The node with the enclosing Class declaration. + * @return The JavaTypeDefinition of the enclosing Class declaration. + */ + private Class getEnclosingTypeDeclaration(Node node) { + Node previousNode = null; while (node != null) { if (node instanceof ASTClassOrInterfaceDeclaration) { return ((TypeNode) node).getType(); - } else if (node instanceof ASTAllocationExpression) { + // anonymous class declaration + } else if (node instanceof ASTAllocationExpression // is anonymous class declaration + && node.getFirstChildOfType(ASTArrayDimsAndInits.class) == null // array cant anonymous + && !(previousNode instanceof ASTArguments)) { // we might come out of the constructor ASTClassOrInterfaceType typeDecl = node.getFirstChildOfType(ASTClassOrInterfaceType.class); - if (typeDecl != null && typeDecl.getType() != null) { + if (typeDecl != null) { return typeDecl.getType(); } } + previousNode = node; node = node.jjtGetParent(); } return null; } - private Field getFirstVisibleFieldFromClass(Class classToSerach, String fieldName, Class accessingClass) { - for ( /* empty */; classToSerach != null; classToSerach = classToSerach.getSuperclass()) { - try { - Field field = classToSerach.getDeclaredField(fieldName); - if (isMemberVisibleFromClass(classToSerach, field.getModifiers(), accessingClass)) { - return field; + /** + * Get the type def. of the super class of the enclosing type declaration which has the same class + * as the second argument, or if the second argument is null, then anonymous classes are considered + * as well and the first enclosing scope's super class is returned. + * + * @param node The node from which to start searching. + * @param clazz The type of the enclosing class. + * @return The TypeDefinition of the superclass. + */ + private JavaTypeDefinition getSuperClassTypeDefinition(Node node, Class clazz) { + Node previousNode = null; + for (; node != null; previousNode = node, node = node.jjtGetParent()) { + if (node instanceof ASTClassOrInterfaceDeclaration // class declaration + // is the class we are looking for or caller requested first class + && (((TypeNode) node).getType() == clazz || clazz == null)) { + + ASTExtendsList extendsList = node.getFirstChildOfType(ASTExtendsList.class); + + if (extendsList != null) { + return ((TypeNode) extendsList.jjtGetChild(0)).getTypeDefinition(); + } else { + return JavaTypeDefinition.build(Object.class); } - } catch (NoSuchFieldException e) { /* swallow */ } + // anonymous class declaration + + } else if (clazz == null // callers requested any class scope + && node instanceof ASTAllocationExpression // is anonymous class decl + && node.getFirstChildOfType(ASTArrayDimsAndInits.class) == null // arrays can't be anonymous + && !(previousNode instanceof ASTArguments)) { // we might come out of the constructor + return node.getFirstChildOfType(ASTClassOrInterfaceType.class).getTypeDefinition(); + } } + return null; } - private boolean isMemberVisibleFromClass(Class classWithMember, int modifiers, Class accessingClass) { - if (accessingClass == null) { - return false; - } - - // public members - if (Modifier.isPublic(modifiers)) { - return true; - } - - Package accessingPackage = accessingClass.getPackage(); - boolean areInTheSamePackage; - if (accessingPackage != null) { - areInTheSamePackage = accessingPackage.getName().startsWith( - classWithMember.getPackage().getName()); - } else { - return false; - } - - // protected members - if (Modifier.isProtected(modifiers) - && (areInTheSamePackage || classWithMember.isAssignableFrom(accessingClass))) { - return true; - } - - // package private - if (!(Modifier.isPrivate(modifiers) || Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers)) - && areInTheSamePackage) { - return true; - } - - // private members - if (Modifier.isPrivate(modifiers) && classWithMember.equals(accessingClass)) { - return true; - } - - return false; - } - @Override public Object visit(ASTPrimaryPrefix node, Object data) { super.visit(node, data); @@ -652,8 +898,60 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter { } @Override - public Object visit(ASTPrimarySuffix node, Object data) { + public Object visit(ASTTypeArgument node, Object data) { super.visit(node, data); + rollupTypeUnary(node); + + if (node.getType() == null) { + // ? extends Something + if (node.jjtGetFirstToken() instanceof Token + && ((Token) node.jjtGetFirstToken()).next.image.equals("extends")) { + + populateType(node, node.jjtGetLastToken().toString()); + + } else { // ? or ? super Something + node.setType(Object.class); + } + } + + return data; + } + + @Override + public Object visit(ASTTypeParameters node, Object data) { + super.visit(node, data); + + if (node.jjtGetParent() instanceof ASTClassOrInterfaceDeclaration) { + TypeNode parent = (TypeNode) node.jjtGetParent(); + + JavaTypeDefinitionBuilder builder = JavaTypeDefinition.builder(parent.getType()); + + for (int childIndex = 0; childIndex < node.jjtGetNumChildren(); ++childIndex) { + builder.addTypeArg(((TypeNode) node.jjtGetChild(childIndex)).getTypeDefinition()); + } + + parent.setTypeDefinition(builder.build()); + } + + return data; + } + + @Override + public Object visit(ASTTypeParameter node, Object data) { + super.visit(node, data); + rollupTypeUnary(node); + + if (node.getType() == null) { + node.setType(Object.class); + } + + return data; + } + + @Override + public Object visit(ASTTypeBound node, Object data) { + super.visit(node, data); + rollupTypeUnary(node); return data; } @@ -770,7 +1068,7 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter { if (node.jjtGetNumChildren() >= 1) { Node child = node.jjtGetChild(0); if (child instanceof TypeNode) { - typeNode.setType(((TypeNode) child).getType()); + typeNode.setTypeDefinition(((TypeNode) child).getTypeDefinition()); } } } @@ -880,11 +1178,44 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter { // ignored } } - if (myType != null) { + + // try generics + // TODO: generic declarations can shadow type declarations ... :( + if (myType == null) { + ASTTypeParameter parameter = getTypeParameterDeclaration(node, className); + if (parameter != null) { + node.setTypeDefinition(parameter.getTypeDefinition()); + } + } else { node.setType(myType); } } + private ASTTypeParameter getTypeParameterDeclaration(Node startNode, String image) { + for (Node parent = startNode.jjtGetParent(); parent != null; parent = parent.jjtGetParent()) { + ASTTypeParameters typeParameters = null; + + if (parent instanceof ASTTypeParameters) { // if type parameter defined in the same < > + typeParameters = (ASTTypeParameters) parent; + } else if (parent instanceof ASTConstructorDeclaration + || parent instanceof ASTMethodDeclaration + || parent instanceof ASTClassOrInterfaceDeclaration) { + typeParameters = parent.getFirstChildOfType(ASTTypeParameters.class); + } + + if (typeParameters != null) { + for (int index = 0; index < typeParameters.jjtGetNumChildren(); ++index) { + String imageToCompareTo = typeParameters.jjtGetChild(index).getImage(); + if (imageToCompareTo != null && imageToCompareTo.equals(image)) { + return (ASTTypeParameter) typeParameters.jjtGetChild(index); + } + } + } + } + + return null; + } + /** * Check whether the supplied class name exists. */ diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/rules/SignatureDeclareThrowsException.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/rules/SignatureDeclareThrowsException.java index 7309ea1515..d041ff6cd6 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/rules/SignatureDeclareThrowsException.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/rules/SignatureDeclareThrowsException.java @@ -7,6 +7,7 @@ package net.sourceforge.pmd.lang.java.typeresolution.rules; import java.util.List; import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ast.ASTAnnotation; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; @@ -113,6 +114,15 @@ public class SignatureDeclareThrowsException extends AbstractJavaRule { if (junitImported && isAllowedMethod(methodDeclaration)) { return super.visit(methodDeclaration, o); } + + // Ignore overridden methods, the issue should be marked on the method definition + final List methodAnnotations = methodDeclaration.jjtGetParent().findChildrenOfType(ASTAnnotation.class); + for (final ASTAnnotation annotation : methodAnnotations) { + final ASTName annotationName = annotation.getFirstDescendantOfType(ASTName.class); + if (annotationName.hasImageEqualTo("Override") || annotationName.hasImageEqualTo("java.lang.Override")) { + return super.visit(methodDeclaration, o); + } + } checkExceptions(methodDeclaration, o); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinition.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinition.java new file mode 100644 index 0000000000..76e3052d13 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinition.java @@ -0,0 +1,74 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.typeresolution.typedefinition; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +public class JavaTypeDefinition implements TypeDefinition { + private final Class clazz; + private List genericArgs; + // contains TypeDefs where only the clazz field is used + private static Map, JavaTypeDefinition> onlyClassTypeDef = new HashMap<>(); + + public Class getType() { + return clazz; + } + + public List getGenericArgs() { + if (genericArgs == null) { + genericArgs = Collections.unmodifiableList(new ArrayList()); + } + + return genericArgs; + } + + private JavaTypeDefinition(Class clazz, List genericArgs) { + this.clazz = clazz; + + if (genericArgs != null) { + this.genericArgs = Collections.unmodifiableList(genericArgs); + } + } + + // builder part of the class + + public static JavaTypeDefinition build(Class clazz) { + if (onlyClassTypeDef.containsKey(clazz)) { + return onlyClassTypeDef.get(clazz); + } + + JavaTypeDefinition typeDef = new JavaTypeDefinition(clazz, null); + + onlyClassTypeDef.put(clazz, typeDef); + + return typeDef; + } + + /** + * @param genericArgs This package private method expects that the genericArgs list has not been leaked, + * meaning the other references have been discarded to ensure immutability. + */ + /* default */ static JavaTypeDefinition build(Class clazz, List genericArgs) { + if (genericArgs == null) { + return build(clazz); + } + + return new JavaTypeDefinition(clazz, genericArgs); + } + + public static JavaTypeDefinitionBuilder builder(Class clazz) { + return new JavaTypeDefinitionBuilder().setType(clazz); + } + + + public static JavaTypeDefinitionBuilder builder() { + return new JavaTypeDefinitionBuilder(); + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinitionBuilder.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinitionBuilder.java new file mode 100644 index 0000000000..b24b4c3b23 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinitionBuilder.java @@ -0,0 +1,39 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.typeresolution.typedefinition; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class JavaTypeDefinitionBuilder { + private Class clazz = null; + private List genericArgs = new ArrayList<>(); + + /* default */ JavaTypeDefinitionBuilder() {} + + public JavaTypeDefinitionBuilder addTypeArg(JavaTypeDefinition arg) { + genericArgs.add(arg); + return this; + } + + public List getTypeArgs() { + return Collections.unmodifiableList(genericArgs); + } + + public JavaTypeDefinitionBuilder getTypeArg(int index) { + genericArgs.get(index); + return this; + } + + public JavaTypeDefinitionBuilder setType(Class clazz) { + this.clazz = clazz; + return this; + } + + public JavaTypeDefinition build() { + return JavaTypeDefinition.build(clazz, genericArgs); + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/TypeDefinition.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/TypeDefinition.java new file mode 100644 index 0000000000..74235f25bd --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/TypeDefinition.java @@ -0,0 +1,23 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.typeresolution.typedefinition; + +import java.util.List; + +public interface TypeDefinition { + /** + * Get the raw Class type of the definition. + * + * @return Raw Class type. + */ + Class getType(); + + /** + * Get the list of type arguments for this TypeDefinition. + * + * @return An ordered and immutable list of type arguments. + */ + List getGenericArgs(); +} diff --git a/pmd-java/src/main/resources/rulesets/java/codesize.xml b/pmd-java/src/main/resources/rulesets/java/codesize.xml index ba41cd6477..6f109353bf 100644 --- a/pmd-java/src/main/resources/rulesets/java/codesize.xml +++ b/pmd-java/src/main/resources/rulesets/java/codesize.xml @@ -148,7 +148,8 @@ public class Foo { since="1.03" message = "The {0} ''{1}'' has a Cyclomatic Complexity of {2}." class="net.sourceforge.pmd.lang.java.rule.codesize.CyclomaticComplexityRule" - externalInfoUrl="${pmd.website.baseurl}/rules/java/codesize.html#CyclomaticComplexity"> + externalInfoUrl="${pmd.website.baseurl}/rules/java/codesize.html#CyclomaticComplexity" + deprecated="true"> + externalInfoUrl="${pmd.website.baseurl}/rules/java/codesize.html#StdCyclomaticComplexity" + deprecated="true"> + externalInfoUrl="${pmd.website.baseurl}/rules/java/codesize.html#ModifiedCyclomaticComplexity" + deprecated="true"> + diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/ClassTypeResolverTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/ClassTypeResolverTest.java index 2497695be0..dcc4e8595c 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/ClassTypeResolverTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/ClassTypeResolverTest.java @@ -6,6 +6,7 @@ package net.sourceforge.pmd.typeresolution; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import java.io.IOException; import java.io.InputStream; @@ -19,8 +20,6 @@ import org.jaxen.JaxenException; import org.junit.Assert; import org.junit.Test; - - import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersionHandler; import net.sourceforge.pmd.lang.ast.Node; @@ -50,13 +49,19 @@ import net.sourceforge.pmd.lang.java.ast.TypeNode; import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; import net.sourceforge.pmd.lang.java.typeresolution.ClassTypeResolver; +import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition; import net.sourceforge.pmd.typeresolution.testdata.AnonymousInnerClass; import net.sourceforge.pmd.typeresolution.testdata.ArrayListFound; import net.sourceforge.pmd.typeresolution.testdata.DefaultJavaLangImport; import net.sourceforge.pmd.typeresolution.testdata.EnumWithAnonymousInnerClass; import net.sourceforge.pmd.typeresolution.testdata.ExtraTopLevelClass; import net.sourceforge.pmd.typeresolution.testdata.FieldAccess; +import net.sourceforge.pmd.typeresolution.testdata.FieldAccessGenericBounds; +import net.sourceforge.pmd.typeresolution.testdata.FieldAccessGenericParameter; +import net.sourceforge.pmd.typeresolution.testdata.FieldAccessGenericRaw; +import net.sourceforge.pmd.typeresolution.testdata.FieldAccessGenericSimple; import net.sourceforge.pmd.typeresolution.testdata.FieldAccessNested; +import net.sourceforge.pmd.typeresolution.testdata.FieldAccessPrimaryGenericSimple; import net.sourceforge.pmd.typeresolution.testdata.FieldAccessShadow; import net.sourceforge.pmd.typeresolution.testdata.FieldAccessSuper; import net.sourceforge.pmd.typeresolution.testdata.InnerClass; @@ -71,8 +76,6 @@ import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassB; import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassB2; - - public class ClassTypeResolverTest { @Test @@ -88,7 +91,7 @@ public class ClassTypeResolverTest { ASTCompilationUnit acu = parseAndTypeResolveForClass15(ArrayListFound.class); assertEquals(ArrayListFound.class, acu.getFirstDescendantOfType(ASTTypeDeclaration.class).getType()); assertEquals(ArrayListFound.class, - acu.getFirstDescendantOfType(ASTClassOrInterfaceDeclaration.class).getType()); + acu.getFirstDescendantOfType(ASTClassOrInterfaceDeclaration.class).getType()); ASTImportDeclaration id = acu.getFirstDescendantOfType(ASTImportDeclaration.class); assertEquals("java.util", id.getPackage().getName()); assertEquals(ArrayList.class, id.getType()); @@ -124,12 +127,12 @@ public class ClassTypeResolverTest { ASTTypeDeclaration typeDeclaration = (ASTTypeDeclaration) acu.jjtGetChild(1); assertEquals(ExtraTopLevelClass.class, typeDeclaration.getType()); assertEquals(ExtraTopLevelClass.class, - typeDeclaration.getFirstDescendantOfType(ASTClassOrInterfaceDeclaration.class).getType()); + typeDeclaration.getFirstDescendantOfType(ASTClassOrInterfaceDeclaration.class).getType()); // Second class typeDeclaration = (ASTTypeDeclaration) acu.jjtGetChild(2); assertEquals(theExtraTopLevelClass, typeDeclaration.getType()); assertEquals(theExtraTopLevelClass, - typeDeclaration.getFirstDescendantOfType(ASTClassOrInterfaceDeclaration.class).getType()); + typeDeclaration.getFirstDescendantOfType(ASTClassOrInterfaceDeclaration.class).getType()); } @Test @@ -144,7 +147,7 @@ public class ClassTypeResolverTest { assertEquals(InnerClass.class, outerClassDeclaration.getType()); // Inner class assertEquals(theInnerClass, - outerClassDeclaration.getFirstDescendantOfType(ASTClassOrInterfaceDeclaration.class).getType()); + outerClassDeclaration.getFirstDescendantOfType(ASTClassOrInterfaceDeclaration.class).getType()); // Method parameter as inner class ASTFormalParameter formalParameter = typeDeclaration.getFirstDescendantOfType(ASTFormalParameter.class); assertEquals(theInnerClass, formalParameter.getTypeNode().getType()); @@ -159,8 +162,10 @@ public class ClassTypeResolverTest { @Test public void testInnerClassNotCompiled() throws Exception { Node acu = parseAndTypeResolveForString("public class TestInnerClass {\n" + " public void foo() {\n" - + " Statement statement = new Statement();\n" + " }\n" + " static class Statement {\n" - + " }\n" + "}", "1.8"); + + " Statement statement = new Statement();\n" + " " + + "}\n" + " static class Statement {\n" + + " }\n" + + "}", "1.8"); ASTClassOrInterfaceType statement = acu.getFirstDescendantOfType(ASTClassOrInterfaceType.class); Assert.assertTrue(statement.isReferenceToClassSameCompilationUnit()); } @@ -178,7 +183,7 @@ public class ClassTypeResolverTest { assertEquals(AnonymousInnerClass.class, outerClassDeclaration.getType()); // Anonymous Inner class assertEquals(theAnonymousInnerClass, - outerClassDeclaration.getFirstDescendantOfType(ASTAllocationExpression.class).getType()); + outerClassDeclaration.getFirstDescendantOfType(ASTAllocationExpression.class).getType()); } @Test @@ -336,7 +341,8 @@ public class ClassTypeResolverTest { ASTCompilationUnit acu = parseAndTypeResolveForClass15(Promotion.class); List expressions = convertList( acu.findChildNodesWithXPath( - "//Block[preceding-sibling::MethodDeclarator[@Image = 'unaryNumericPromotion']]//Expression[UnaryExpression]"), + "//Block[preceding-sibling::MethodDeclarator[@Image = " + + "'unaryNumericPromotion']]//Expression[UnaryExpression]"), ASTExpression.class); int index = 0; @@ -357,7 +363,8 @@ public class ClassTypeResolverTest { ASTCompilationUnit acu = parseAndTypeResolveForClass15(Promotion.class); List expressions = convertList( acu.findChildNodesWithXPath( - "//Block[preceding-sibling::MethodDeclarator[@Image = 'binaryNumericPromotion']]//Expression[AdditiveExpression]"), + "//Block[preceding-sibling::MethodDeclarator[@Image = " + + "'binaryNumericPromotion']]//Expression[AdditiveExpression]"), ASTExpression.class); int index = 0; @@ -494,15 +501,18 @@ public class ClassTypeResolverTest { TypeNode.class)); expressions.addAll(convertList( acu.findChildNodesWithXPath( - "//Block[preceding-sibling::MethodDeclarator[@Image = 'unaryNumericOperators']]//PostfixExpression"), + "//Block[preceding-sibling::MethodDeclarator[@Image = " + + "'unaryNumericOperators']]//PostfixExpression"), TypeNode.class)); expressions.addAll(convertList( acu.findChildNodesWithXPath( - "//Block[preceding-sibling::MethodDeclarator[@Image = 'unaryNumericOperators']]//PreIncrementExpression"), + "//Block[preceding-sibling::MethodDeclarator[@Image = " + + "'unaryNumericOperators']]//PreIncrementExpression"), TypeNode.class)); expressions.addAll(convertList( acu.findChildNodesWithXPath( - "//Block[preceding-sibling::MethodDeclarator[@Image = 'unaryNumericOperators']]//PreDecrementExpression"), + "//Block[preceding-sibling::MethodDeclarator[@Image = " + + "'unaryNumericOperators']]//PreDecrementExpression"), TypeNode.class)); int index = 0; @@ -552,7 +562,8 @@ public class ClassTypeResolverTest { ASTCompilationUnit acu = parseAndTypeResolveForClass15(Operators.class); List expressions = convertList( acu.findChildNodesWithXPath( - "//Block[preceding-sibling::MethodDeclarator[@Image = 'assignmentOperators']]//StatementExpression"), + "//Block[preceding-sibling::MethodDeclarator[@Image = " + + "'assignmentOperators']]//StatementExpression"), ASTStatementExpression.class); int index = 0; @@ -656,19 +667,19 @@ public class ClassTypeResolverTest { assertEquals(SuperClassA.class, expressions.get(index++).getType()); assertEquals(SuperClassA.class, expressions.get(index++).getType()); assertEquals(SuperClassA.class, expressions.get(index++).getType()); - assertEquals(SuperExpression.class, ((TypeNode) expressions.get(index).jjtGetParent().jjtGetChild(0)).getType()); + assertEquals(SuperExpression.class, ((TypeNode) expressions.get(index).jjtGetParent().jjtGetChild(0)) + .getType()); assertEquals(SuperClassA.class, ((TypeNode) expressions.get(index++).jjtGetParent().jjtGetChild(1)).getType()); assertEquals(SuperExpression.class, expressions.get(index++).getType()); assertEquals(SuperExpression.class, expressions.get(index++).getType()); - + // Make sure we got them all assertEquals("All expressions not tested", index, expressions.size()); } - @Test public void testFieldAccess() throws JaxenException { ASTCompilationUnit acu = parseAndTypeResolveForClass15(FieldAccess.class); @@ -730,6 +741,12 @@ public class ClassTypeResolverTest { assertEquals(FieldAccessNested.Nested.class, getChildType(expressions.get(index), 1)); assertEquals(SuperClassA.class, getChildType(expressions.get(index++), 2)); + // FieldAccessNested.Nested.this.a = new SuperClassA(); + assertEquals(SuperClassA.class, expressions.get(index).getType()); + assertEquals(FieldAccessNested.Nested.class, getChildType(expressions.get(index), 0)); + assertEquals(FieldAccessNested.Nested.class, getChildType(expressions.get(index), 1)); + assertEquals(SuperClassA.class, getChildType(expressions.get(index++), 2)); + // Make sure we got them all assertEquals("All expressions not tested", index, expressions.size()); } @@ -829,15 +846,275 @@ public class ClassTypeResolverTest { assertEquals("All expressions not tested", index, expressions.size()); } - private Class getChildType(Node node, int childIndex) { - if (node.jjtGetNumChildren() > childIndex) { - Node child = node.jjtGetChild(childIndex); - if (child instanceof TypeNode) { - return ((TypeNode) child).getType(); - } - } + @Test + public void testBoundsGenericFieldAccess() throws JaxenException { + ASTCompilationUnit acu = parseAndTypeResolveForClass15(FieldAccessGenericBounds.class); - return null; + List expressions = convertList( + acu.findChildNodesWithXPath("//StatementExpression/PrimaryExpression"), + AbstractJavaTypeNode.class); + + + int index = 0; + + // superGeneric.first = ""; // Object + assertEquals(Object.class, expressions.get(index).getType()); + assertEquals(Object.class, getChildType(expressions.get(index++), 0)); + + // superGeneric.second = null; // Object + assertEquals(Object.class, expressions.get(index).getType()); + assertEquals(Object.class, getChildType(expressions.get(index++), 0)); + + // inheritedSuperGeneric.first = ""; // Object + assertEquals(Object.class, expressions.get(index).getType()); + assertEquals(Object.class, getChildType(expressions.get(index++), 0)); + + // inheritedSuperGeneric.second = null; // Object + assertEquals(Object.class, expressions.get(index).getType()); + assertEquals(Object.class, getChildType(expressions.get(index++), 0)); + + // upperBound.first = null; // Number + assertEquals(Number.class, expressions.get(index).getType()); + assertEquals(Number.class, getChildType(expressions.get(index++), 0)); + + // inheritedUpperBound.first = null; // String + assertEquals(String.class, expressions.get(index).getType()); + assertEquals(String.class, getChildType(expressions.get(index++), 0)); + + + // Make sure we got them all + assertEquals("All expressions not tested", index, expressions.size()); + } + + @Test + public void testParameterGenericFieldAccess() throws JaxenException { + ASTCompilationUnit acu = parseAndTypeResolveForClass15(FieldAccessGenericParameter.class); + + List expressions = convertList( + acu.findChildNodesWithXPath("//StatementExpression/PrimaryExpression"), + AbstractJavaTypeNode.class); + + + int index = 0; + + // classGeneric = null; // Double + assertEquals(Double.class, expressions.get(index).getType()); + assertEquals(Double.class, getChildType(expressions.get(index++), 0)); + + // localGeneric = null; // Character + assertEquals(Character.class, expressions.get(index).getType()); + assertEquals(Character.class, getChildType(expressions.get(index++), 0)); + + // parameterGeneric.second.second = new Integer(0); + assertEquals(Integer.class, expressions.get(index).getType()); + assertEquals(Integer.class, getChildType(expressions.get(index++), 0)); + + // localGeneric = null; // Number + assertEquals(Number.class, expressions.get(index).getType()); + assertEquals(Number.class, getChildType(expressions.get(index++), 0)); + + // Make sure we got them all + assertEquals("All expressions not tested", index, expressions.size()); + } + + @Test + public void testSimpleGenericFieldAccess() throws JaxenException { + ASTCompilationUnit acu = parseAndTypeResolveForClass15(FieldAccessGenericSimple.class); + + List expressions = convertList( + acu.findChildNodesWithXPath("//StatementExpression/PrimaryExpression"), + AbstractJavaTypeNode.class); + + + int index = 0; + + // genericField.first = ""; + assertEquals(String.class, expressions.get(index).getType()); + assertEquals(String.class, getChildType(expressions.get(index++), 0)); + + // genericField.second = new Double(0); + assertEquals(Double.class, expressions.get(index).getType()); + assertEquals(Double.class, getChildType(expressions.get(index++), 0)); + + //genericTypeArg.second.second = new Double(0); + assertEquals(Double.class, expressions.get(index).getType()); + assertEquals(Double.class, getChildType(expressions.get(index++), 0)); + + // param.first = new Integer(0); + assertEquals(Integer.class, expressions.get(index).getType()); + assertEquals(Integer.class, getChildType(expressions.get(index++), 0)); + + // local.second = new Long(0); + assertEquals(Long.class, expressions.get(index).getType()); + assertEquals(Long.class, getChildType(expressions.get(index++), 0)); + + + // param.generic.first = new Character('c'); + assertEquals(Character.class, expressions.get(index).getType()); + assertEquals(Character.class, getChildType(expressions.get(index++), 0)); + + // local.generic.second = new Float(0); + assertEquals(Float.class, expressions.get(index).getType()); + assertEquals(Float.class, getChildType(expressions.get(index++), 0)); + + // genericField.generic.generic.generic.first = new Double(0); + assertEquals(Double.class, expressions.get(index).getType()); + assertEquals(Double.class, getChildType(expressions.get(index++), 0)); + + // fieldA = new Long(0); + assertEquals(Long.class, expressions.get(index).getType()); + assertEquals(Long.class, getChildType(expressions.get(index++), 0)); + + // fieldB.generic.second = ""; + assertEquals(String.class, expressions.get(index).getType()); + assertEquals(String.class, getChildType(expressions.get(index++), 0)); + + // fieldAcc.fieldA = new Long(0); + assertEquals(Long.class, expressions.get(index).getType()); + assertEquals(Long.class, getChildType(expressions.get(index++), 0)); + + // fieldA = new Long(0); + assertEquals(Long.class, expressions.get(index).getType()); + assertEquals(Long.class, getChildType(expressions.get(index++), 0)); + + // Make sure we got them all + assertEquals("All expressions not tested", index, expressions.size()); + } + + @Test + public void testRawGenericFieldAccess() throws JaxenException { + ASTCompilationUnit acu = parseAndTypeResolveForClass15(FieldAccessGenericRaw.class); + + List expressions = convertList( + acu.findChildNodesWithXPath("//StatementExpression/PrimaryExpression"), + AbstractJavaTypeNode.class); + + + int index = 0; + + // rawGeneric.first = new Integer(0); + assertEquals(Integer.class, expressions.get(index).getType()); + assertEquals(Integer.class, getChildType(expressions.get(index++), 0)); + + // rawGeneric.second = new Integer(0); + assertEquals(Integer.class, expressions.get(index).getType()); + assertEquals(Integer.class, getChildType(expressions.get(index++), 0)); + + // rawGeneric.third = new Object(); + assertEquals(Object.class, expressions.get(index).getType()); + assertEquals(Object.class, getChildType(expressions.get(index++), 0)); + // rawGeneric.fourth.second = ""; + assertEquals(String.class, expressions.get(index).getType()); + assertEquals(String.class, getChildType(expressions.get(index++), 0)); + // rawGeneric.rawGeneric.second = new Integer(0); + assertEquals(Integer.class, expressions.get(index).getType()); + assertEquals(Integer.class, getChildType(expressions.get(index++), 0)); + // inheritedRawGeneric.first = new Integer(0); + assertEquals(Integer.class, expressions.get(index).getType()); + assertEquals(Integer.class, getChildType(expressions.get(index++), 0)); + // inheritedRawGeneric.second = new Integer(0); + assertEquals(Integer.class, expressions.get(index).getType()); + assertEquals(Integer.class, getChildType(expressions.get(index++), 0)); + // inheritedRawGeneric.third = new Object(); + assertEquals(Object.class, expressions.get(index).getType()); + assertEquals(Object.class, getChildType(expressions.get(index++), 0)); + // inheritedRawGeneric.fourth.second = ""; + assertEquals(String.class, expressions.get(index).getType()); + assertEquals(String.class, getChildType(expressions.get(index++), 0)); + // inheritedRawGeneric.rawGeneric.second = new Integer(0); + assertEquals(Integer.class, expressions.get(index).getType()); + assertEquals(Integer.class, getChildType(expressions.get(index++), 0)); + // parameterRawGeneric.first = new Integer(0); + assertEquals(Integer.class, expressions.get(index).getType()); + assertEquals(Integer.class, getChildType(expressions.get(index++), 0)); + // parameterRawGeneric.second = new Integer(0); + assertEquals(Integer.class, expressions.get(index).getType()); + assertEquals(Integer.class, getChildType(expressions.get(index++), 0)); + // parameterRawGeneric.third = new Object(); + assertEquals(Object.class, expressions.get(index).getType()); + assertEquals(Object.class, getChildType(expressions.get(index++), 0)); + // parameterRawGeneric.fourth.second = ""; + assertEquals(String.class, expressions.get(index).getType()); + assertEquals(String.class, getChildType(expressions.get(index++), 0)); + // parameterRawGeneric.rawGeneric.second = new Integer(0); + assertEquals(Integer.class, expressions.get(index).getType()); + assertEquals(Integer.class, getChildType(expressions.get(index++), 0)); + + + // Make sure we got them all + assertEquals("All expressions not tested", index, expressions.size()); + } + + @Test + public void testPrimarySimpleGenericFieldAccess() throws JaxenException { + ASTCompilationUnit acu = parseAndTypeResolveForClass15(FieldAccessPrimaryGenericSimple.class); + + List expressions = convertList( + acu.findChildNodesWithXPath("//StatementExpression/PrimaryExpression"), + AbstractJavaTypeNode.class); + + + int index = 0; + + JavaTypeDefinition typeDef; + + + // this.genericField.first = ""; + assertEquals(String.class, expressions.get(index).getType()); + assertChildTypeArgsEqualTo(expressions.get(index), 1, String.class, Double.class); + assertEquals(String.class, getChildType(expressions.get(index++), 2)); + + // (this).genericField.second = new Double(0); + assertEquals(Double.class, expressions.get(index).getType()); + assertChildTypeArgsEqualTo(expressions.get(index), 1, String.class, Double.class); + assertEquals(Double.class, getChildType(expressions.get(index++), 2)); + + // this.genericTypeArg.second.second = new Double(0); + assertEquals(Double.class, expressions.get(index).getType()); + assertChildTypeArgsEqualTo(expressions.get(index), 2, Number.class, Double.class); + assertEquals(Double.class, getChildType(expressions.get(index++), 3)); + + // (this).genericField.generic.generic.generic.first = new Double(0); + assertEquals(Double.class, expressions.get(index).getType()); + assertEquals(Double.class, getChildType(expressions.get(index++), 5)); + + // (this).fieldA = new Long(0); + assertEquals(Long.class, expressions.get(index).getType()); + assertEquals(Long.class, getChildType(expressions.get(index++), 1)); + + // this.fieldB.generic.second = ""; + assertEquals(String.class, expressions.get(index).getType()); + assertEquals(String.class, getChildType(expressions.get(index++), 3)); + + // super.fieldA = new Long(0); + assertEquals(Long.class, expressions.get(index).getType()); + assertChildTypeArgsEqualTo(expressions.get(index), 0, Long.class); + assertEquals(Long.class, getChildType(expressions.get(index++), 1)); + + // super.fieldB.generic.second = ""; + assertEquals(String.class, expressions.get(index).getType()); + assertEquals(String.class, getChildType(expressions.get(index++), 3)); + + // this.field.first = ""; + assertEquals(String.class, expressions.get(index).getType()); + assertEquals(String.class, getChildType(expressions.get(index++), 2)); + + // Make sure we got them all + assertEquals("All expressions not tested", index, expressions.size()); + } + + private Class getChildType(Node node, int childIndex) { + return ((TypeNode) node.jjtGetChild(childIndex)).getType(); + } + + private void assertChildTypeArgsEqualTo(Node node, int childIndex, Class... classes) { + JavaTypeDefinition typeDef = ((TypeNode) node.jjtGetChild(childIndex)).getTypeDefinition(); + + assertTrue(typeDef.getGenericArgs().size() == classes.length); + + for (int index = 0; index < classes.length; ++index) { + assertTrue(typeDef.getGenericArgs().get(index).getType() == classes[index]); + } } private ASTCompilationUnit parseAndTypeResolveForClass15(Class clazz) { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccess.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccess.java index 204d6e6287..6a8eaa806b 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccess.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccess.java @@ -12,10 +12,13 @@ import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassA; /* * Note: inherited fields of a nested class shadow outer scope variables * Note: only if they are accessible! + * + * TODO: test static field access, array types, anonymous class (super type access) */ public class FieldAccess extends SuperClassA { public int field; public FieldAccess f; + public static FieldAccess staticF; public void foo(FieldAccess param) { FieldAccess local = null; diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccessGenericBounds.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccessGenericBounds.java new file mode 100644 index 0000000000..2731c6f208 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccessGenericBounds.java @@ -0,0 +1,32 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata; + +import net.sourceforge.pmd.typeresolution.testdata.dummytypes.GenericClass; +import net.sourceforge.pmd.typeresolution.testdata.dummytypes.GenericSuperClassA; + + +public class FieldAccessGenericBounds extends GenericSuperClassA { + GenericClass superGeneric; + GenericClass upperBound; + + public void astPrimaryNameCases() { + // test ?, ? super Something, ? extends Something + // Primary[Prefix[Name[superGeneric.first]]] + superGeneric.first = ""; // Object + superGeneric.second = null; // Object + inheritedSuperGeneric.first = ""; // Object + inheritedSuperGeneric.second = null; // Object + + upperBound.first = null; // Number + inheritedUpperBound.first = null; // String + + // test static imports + // Primary[Prefix[Name[instanceFields.generic.first]]] + //instanceFields.generic.first = ""; + //staticGeneric.first = new Long(0); + } +} + diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccessGenericParameter.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccessGenericParameter.java new file mode 100644 index 0000000000..6f273a0198 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccessGenericParameter.java @@ -0,0 +1,35 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata; + +import net.sourceforge.pmd.typeresolution.testdata.dummytypes.GenericClass; + +public class FieldAccessGenericParameter>, + S extends Double> { + T parameterGeneric; + S classGeneric; + + void foo() { + M localGeneric = null; + + // access type dependant on class/method type arguments + // Primary[Prefix[Name[classGeneric]]] + classGeneric = null; // Double + localGeneric = null; // Character + + + // test type parameters extending generic types + // Primary[Prefix[Name[parameterGeneric.first]]] + parameterGeneric.second.second = new Integer(0); + } + + FieldAccessGenericParameter() { + C constructorGeneric = null; + + // access type dependant on constructor type arugments + // Primary[Prefix[Name[localGeneric]]] + constructorGeneric = null; // Number + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccessGenericRaw.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccessGenericRaw.java new file mode 100644 index 0000000000..7cf7cb9a8a --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccessGenericRaw.java @@ -0,0 +1,37 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata; + +import net.sourceforge.pmd.typeresolution.testdata.dummytypes.GenericClass2; +import net.sourceforge.pmd.typeresolution.testdata.dummytypes.GenericSuperClassA; + +public class FieldAccessGenericRaw extends GenericSuperClassA { + GenericClass2 rawGeneric; + T parameterRawGeneric; + + void foo() { + // test raw types + // Primary[Prefix[Name[rawGeneric.first]]] + rawGeneric.first = new Integer(0); + rawGeneric.second = new Integer(0); + rawGeneric.third = new Object(); + rawGeneric.fourth.second = ""; + rawGeneric.rawGeneric.second = new Integer(0); + + // Primary[Prefix[Name[inheritedGeneric.first]]] + inheritedRawGeneric.first = new Integer(0); + inheritedRawGeneric.second = new Integer(0); + inheritedRawGeneric.third = new Object(); + inheritedRawGeneric.fourth.second = ""; + inheritedRawGeneric.rawGeneric.second = new Integer(0); + + // Primary[Prefix[Name[parameterRawGeneric.first]]] + parameterRawGeneric.first = new Integer(0); + parameterRawGeneric.second = new Integer(0); + parameterRawGeneric.third = new Object(); + parameterRawGeneric.fourth.second = ""; + parameterRawGeneric.rawGeneric.second = new Integer(0); + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccessGenericSimple.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccessGenericSimple.java new file mode 100644 index 0000000000..198b9fdb10 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccessGenericSimple.java @@ -0,0 +1,60 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata; + +import net.sourceforge.pmd.typeresolution.testdata.dummytypes.GenericClass; +import net.sourceforge.pmd.typeresolution.testdata.dummytypes.GenericSuperClassA; + + +/* + * TODO: add anonymous class this (Allocation expression) + * TODO: add primitives, parameterized arrays + * TODO: diamond, type parmeter declarations can shadow Class declarations + */ + +public class FieldAccessGenericSimple extends GenericSuperClassA { + GenericClass genericField; + GenericClass> genericTypeArg; + FieldAccessGenericSimple fieldAcc; + + void foo(GenericClass param) { + GenericClass local = null; + + // access a generic field through member field + // Primary[Prefix[Name[genericField.first]]] + genericField.first = ""; + genericField.second = new Double(0); + + // access a generic field whose type depends on a generic type argument + // Primary[Prefix[Name[genericTypeArg.second.second]]] + genericTypeArg.second.second = new Double(0); + + // access a generic field through a local or a parameter + // Primary[Prefix[Name[param.first]]] + param.first = new Integer(0); + local.second = new Long(0); + + // access a generic field whose type depends on indirect type arguments + // Primary[Prefix[Name[generic.generic.first]]] + param.generic.first = new Character('c'); + local.generic.second = new Float(0); + genericField.generic.generic.generic.first = new Double(0); + + // test inherited generic + // Primary[Prefix[Name[generic.first]]] + fieldA = new Long(0); + fieldB.generic.second = ""; + + // test inherited generic + // Primary[Prefix[Name[fieldAcc.fieldA]]] + fieldAcc.fieldA = new Long(0); + } + + public class Nested extends GenericSuperClassA { + void foo() { + fieldA = new Long(0); + } + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccessNested.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccessNested.java index 2537e4054e..6c6ca983b4 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccessNested.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccessNested.java @@ -29,6 +29,7 @@ public class FieldAccessNested { a = new SuperClassA(); net.sourceforge.pmd.typeresolution.testdata.FieldAccessNested.Nested.this.a = new SuperClassA(); + FieldAccessNested.Nested.this.a = new SuperClassA(); } } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccessPrimaryGenericSimple.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccessPrimaryGenericSimple.java new file mode 100644 index 0000000000..8480f69b14 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccessPrimaryGenericSimple.java @@ -0,0 +1,49 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata; + +import net.sourceforge.pmd.typeresolution.testdata.dummytypes.GenericClass; +import net.sourceforge.pmd.typeresolution.testdata.dummytypes.GenericSuperClassA; + +public class FieldAccessPrimaryGenericSimple extends GenericSuperClassA { + GenericClass genericField; + GenericClass> genericTypeArg; + + void foo(GenericClass param) { + GenericClass local = null; + + // access a generic field through member field + // Primary[Prefix[this], Suffix[genericField], Suffix[first]] + this.genericField.first = ""; + (this).genericField.second = new Double(0); + + // access a generic field whose type depends on a generic type argument + // Primary[Prefix[this], Suffix[genericTypeArg], Suffix[second], Suffix[second]] + this.genericTypeArg.second.second = new Double(0); + + // access a generic field whose type depends on indirect type arguments + // Primary[Prefix[this], Suffix[genericField], Suffix[generic], Suffix[generic]...] + (this).genericField.generic.generic.generic.first = new Double(0); + + // test inherited generic + // Primary[Primary[Prefix[(this)]], Suffix[fieldA]] + (this).fieldA = new Long(0); + this.fieldB.generic.second = ""; + + // test inherited generic + // Primary[Prefix[super], Suffix[fieldA]] + super.fieldA = new Long(0); + super.fieldB.generic.second = ""; + } + + class Nested> { + T field; + + void foo() { + // Primary[Prefix[this], Suffix[field], Suffix[first]] + this.field.first = ""; + } + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccessShadow.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccessShadow.java index 7ec5380549..99d27d0ccd 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccessShadow.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/FieldAccessShadow.java @@ -16,13 +16,8 @@ import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassB2; */ public class FieldAccessShadow { Integer field; - String s2; - - - - public void foo() { String field; diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/GenericClass.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/GenericClass.java index 7f2e480764..6f43c42dc2 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/GenericClass.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/GenericClass.java @@ -5,6 +5,8 @@ package net.sourceforge.pmd.typeresolution.testdata.dummytypes; -public class GenericClass { - public T a; +public class GenericClass { + public T first; + public S second; + public GenericClass generic; } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/GenericClass2.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/GenericClass2.java new file mode 100644 index 0000000000..e922dcaea3 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/GenericClass2.java @@ -0,0 +1,19 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata.dummytypes; + +public class GenericClass2, + //, E extends GenericClass, + F extends GenericClass2> { + public A first; + public B second; + public C third; + public D fourth; + //public E fifth; // recursion + public F sixth; // recursion + public GenericClass2 rawGeneric; +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/GenericSuperClassA.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/GenericSuperClassA.java new file mode 100644 index 0000000000..d8ad8d17b7 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/GenericSuperClassA.java @@ -0,0 +1,12 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata.dummytypes; + +public class GenericSuperClassA extends GenericSuperClassB> { + public T fieldA; + public GenericClass2 inheritedRawGeneric; + public GenericClass inheritedSuperGeneric; + public GenericClass inheritedUpperBound; +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/GenericSuperClassB.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/GenericSuperClassB.java new file mode 100644 index 0000000000..62de0a7837 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/GenericSuperClassB.java @@ -0,0 +1,9 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata.dummytypes; + +public class GenericSuperClassB { + public S fieldB; +} diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyCatchBlock.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyCatchBlock.xml index 9580f53c70..5dc0869eb9 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyCatchBlock.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyCatchBlock.xml @@ -167,4 +167,21 @@ Javadoc comment is not OK + + + ^(ignored|expected)$ + 0 + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyTryBlock.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyTryBlock.xml index c671f1d1c0..8e4a64079e 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyTryBlock.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyTryBlock.xml @@ -46,6 +46,19 @@ public class EmptyTryBlock3 { int x = 5; } } +} + ]]> + + + #432 false positive for empty try-with-resource + 0 + target.request(mediaTypes).delete(), DELETE, new ExpectedResponse(status, required))) { + // false positive + } + } } ]]> diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/xml/JUnitTestsShouldIncludeAssert.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/xml/JUnitTestsShouldIncludeAssert.xml index 61f5047edc..9014686439 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/xml/JUnitTestsShouldIncludeAssert.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/xml/JUnitTestsShouldIncludeAssert.xml @@ -392,6 +392,26 @@ public class FooTest { Mockito.verify(bar, Mockito.times(1)).actuallyDoTask(); } +}]]> + + + #465 NullPointerException when dealing with @SuppressWarnings + 0 + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjava/xml/InvalidSlf4jMessageFormat.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjava/xml/InvalidSlf4jMessageFormat.xml index ac0f174986..38f74f2666 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjava/xml/InvalidSlf4jMessageFormat.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjava/xml/InvalidSlf4jMessageFormat.xml @@ -198,6 +198,26 @@ public class TestBug1551 { return "message"; } +} + ]]> + + + + #365 [java] InvalidSlf4jMessageFormat: false positive with pre-incremented variable + 0 + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/SignatureDeclareThrowsException.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/SignatureDeclareThrowsException.xml index f646a49cb1..1b12f2deab 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/SignatureDeclareThrowsException.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/SignatureDeclareThrowsException.xml @@ -103,4 +103,16 @@ public class BugSignature { public class UnmodifiableList implements @Readonly List<@Readonly T> {} ]]> + + + #350 allow throws exception when overriding a method defined elsewhere + 0 + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/SignatureDeclareThrowsException.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/SignatureDeclareThrowsException.xml index 76780a567e..9c042f1cab 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/SignatureDeclareThrowsException.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/SignatureDeclareThrowsException.xml @@ -151,4 +151,16 @@ public class Foo { public class UnmodifiableList implements @Readonly List<@Readonly T> {} ]]> + + + #350 allow throws exception when overriding a method defined elsewhere + 0 + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/UnusedImports.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/UnusedImports.xml index 2deb2d9a11..2561d9acb4 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/UnusedImports.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/UnusedImports.xml @@ -261,6 +261,21 @@ public interface Interface { */ void doSomething(); +} + ]]> + + + #348 False Positive UnusedImports with javadoc for public static inner classes of imports + 0 + diff --git a/pmd-jsp/etc/grammar/JspParser.jjt b/pmd-jsp/etc/grammar/JspParser.jjt index 13863d8247..7ea94dcf5f 100644 --- a/pmd-jsp/etc/grammar/JspParser.jjt +++ b/pmd-jsp/etc/grammar/JspParser.jjt @@ -1,4 +1,8 @@ -/* +/* + * Allow boolean attributes + * + * Juan Martín Sotuyo Dodero 06/2017 + *==================================================================== * Added capability for Tracking Tokens. * * Amit Kumar Prasad 10/2015 @@ -580,7 +584,7 @@ void Attribute() : ( AttributeValue() - ) + )? } /** diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/JspParserTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/JspParserTest.java index 4d7d221f3b..b05018c207 100644 --- a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/JspParserTest.java +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/JspParserTest.java @@ -29,6 +29,16 @@ public class JspParserTest { "$129.00"); Assert.assertNotNull(node); } + + /** + * Verifies bug #311 Jsp parser fails on boolean attribute + */ + @Test + public void testParseBooleanAttribute() { + Node node = parse( + ""); + Assert.assertNotNull(node); + } private Node parse(String code) { LanguageVersionHandler jspLang = LanguageRegistry.getLanguage(JspLanguageModule.NAME).getDefaultVersion() diff --git a/src/site/markdown/overview/changelog.md b/src/site/markdown/overview/changelog.md index fd12d4dc9b..3124cbbf0d 100644 --- a/src/site/markdown/overview/changelog.md +++ b/src/site/markdown/overview/changelog.md @@ -10,22 +10,40 @@ This is a minor release. * [New and noteworthy](#New_and_noteworthy) * [Java Type Resolution](#Java_Type_Resolution) + * [Metrics Framework](#Metrics_Framework) * [Modified Rules](#Modified_Rules) + * [Deprecated Rules](#Deprecated_Rules) * [Fixed Issues](#Fixed_Issues) * [API Changes](#API_Changes) * [External Contributions](#External_Contributions) ### New and noteworthy -### Java Type Resolution +#### Java Type Resolution -As part of Google Summer of Code 2017, [Bendeguz Nagy](https://github.com/WinterGrascph) has been working on completing type resolution for Java. +As part of Google Summer of Code 2017, [Bendegúz Nagy](https://github.com/WinterGrascph) has been working on completing type resolution for Java. His progress so far has allowed to properly resolve, in addition to previously supported statements: - References to `this` and `super`, even when qualified - References to fields, even when chained (ie: `this.myObject.aField`), and properly handling inheritance / shadowing -Fields using generics are still Work in Progress, but we expect to fully support it soon enough. +Lambda parameter types where these are infered rather than explicit are still not supported. Expect future releases to do so. + + +#### Metrics Framework + +As part of Google Summer of Code 2017, [Clément Fournier](https://github.com/oowekyala) has been working on +a new metrics framework for object-oriented metrics. + +The basic groundwork has been done already and with this release, including a first rule based on the +metrics framework as a proof-of-concept: The rule *CyclomaticComplexity*, currently in the temporary +ruleset *java-metrics*, uses the Cyclomatic Complexity metric to find overly complex code. +This rule will eventually replace the existing three *CyclomaticComplexity* rules that are currently +defined in the *java-codesize* ruleset (see also [issue #445](https://github.com/pmd/pmd/issues/445)). + +Since this work is still in progress, the metrics API (package `net.sourceforge.pmd.lang.java.oom`) +is not finalized yet and is expected to change. + #### Modified Rules @@ -39,6 +57,18 @@ Fields using generics are still Work in Progress, but we expect to fully support * The ruleset java-junit now properly detects JUnit5, and rules are being adapted to the changes on it's API. This support is, however, still incomplete. Let us know of any uses we are still missing on the [issue tracker](https://github.com/pmd/pmd/issues) +* The Java rule `EmptyTryBlock` (ruleset java-empty) now allows empty blocks when using try-with-resources. + +* The Java rule `EmptyCatchBlock` (ruleset java-empty) now exposes a new property called `allowExceptionNameRegex`. + This allow to setup a regular expression for names of exceptions you wish to ignore for this rule. For instance, + setting it to `^(ignored|expected)$` would ignore all empty catch blocks where the catched exception is named + either `ignored` or `expected`. The default ignores no exceptions, being backwards compatible. + +#### Deprecated Rules + +* The three complexity rules `CyclomaticComplexity`, `StdCyclomaticComplexity`, `ModifiedCyclomaticComplexity` (ruleset java-codesize) have been deprecated. They will be eventually replaced +by a new CyclomaticComplexity rule based on the metrics framework. See also [issue #445](https://github.com/pmd/pmd/issues/445). + ### Fixed Issues * General @@ -57,10 +87,24 @@ Fields using generics are still Work in Progress, but we expect to fully support * [#397](https://github.com/pmd/pmd/issues/397): \[java] ConstructorCallsOverridableMethodRule: false positive for method called from lambda expression * [#410](https://github.com/pmd/pmd/issues/410): \[java] ImmutableField: False positive with lombok * [#422](https://github.com/pmd/pmd/issues/422): \[java] PreserveStackTraceRule: false positive when using builder pattern +* java-empty + * [#413](https://github.com/pmd/pmd/issues/413): \[java] EmptyCatchBlock don't fail when exception is named ignore / expected + * [#432](https://github.com/pmd/pmd/issues/432): \[java] EmptyTryBlock: false positive for empty try-with-resource +* java-imports: + * [#348](https://github.com/pmd/pmd/issues/348): \[java] imports/UnusedImport rule not considering static inner classes of imports * java-junit * [#428](https://github.com/pmd/pmd/issues/428): \[java] PMD requires public modifier on JUnit 5 test + * [#465](https://github.com/pmd/pmd/issues/465): \[java] NullPointerException in JUnitTestsShouldIncludeAssertRule +* java-logging: + * [#365](https://github.com/pmd/pmd/issues/365): \[java] InvalidSlf4jMessageFormat does not handle inline incrementation of arguments +* java-strictexceptions + * [#350](https://github.com/pmd/pmd/issues/350): \[java] Throwing Exception in method signature is fine if the method is overriding or implementing something +* java-typeresolution + * [#350](https://github.com/pmd/pmd/issues/350): \[java] Throwing Exception in method signature is fine if the method is overriding or implementing something * java-unnecessary * [#421](https://github.com/pmd/pmd/issues/421): \[java] UnnecessaryFinalModifier final in private method +* jsp + * [#311](https://github.com/pmd/pmd/issues/311): \[jsp] Parse error on HTML boolean attribute ### API Changes @@ -76,4 +120,6 @@ Fields using generics are still Work in Progress, but we expect to fully support * [#436](https://github.com/pmd/pmd/pull/436): \[java] Metrics framework tests and various improvements * [#440](https://github.com/pmd/pmd/pull/440): \[core] Created ruleset schema 3.0.0 (to use metrics) * [#443](https://github.com/pmd/pmd/pull/443): \[java] Optimize typeresolution, by skipping package and import declarations in visit(ASTName) +* [#444](https://github.com/pmd/pmd/pull/444): \[java] [typeresolution]: add support for generic fields +* [#451](https://github.com/pmd/pmd/pull/451): \[java] Metrics framework: first metrics + first rule