diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/internal/util/AssertionUtil.java b/pmd-core/src/main/java/net/sourceforge/pmd/internal/util/AssertionUtil.java
index 12397636eb..c4947441b1 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/internal/util/AssertionUtil.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/internal/util/AssertionUtil.java
@@ -21,6 +21,28 @@ public final class AssertionUtil {
return PACKAGE_PATTERN.matcher(name).matches();
}
+ /**
+ * Returns true if the charsequence is a valid java identifier.
+ *
+ * @param name Name (non-null)
+ *
+ * @throws NullPointerException If the name is null
+ */
+ public static boolean isJavaIdentifier(CharSequence name) {
+ int len = name.length();
+ if (len == 0 || !Character.isJavaIdentifierStart(name.charAt(0))) {
+ return false;
+ }
+
+ for (int i = 1; i < len; i++) {
+ if (!Character.isJavaIdentifierPart(name.charAt(i))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
public static int requireOver1(String name, final int value) {
if (value < 1) {
throw mustBe(name, value, ">= 1");
diff --git a/pmd-java/etc/grammar/Java.jjt b/pmd-java/etc/grammar/Java.jjt
index 728705fb7a..17f6d13d10 100644
--- a/pmd-java/etc/grammar/Java.jjt
+++ b/pmd-java/etc/grammar/Java.jjt
@@ -2393,7 +2393,7 @@ void RSIGNEDSHIFT() #void:
void Annotation():
{}
{
- "@" jjtThis.name=VoidName() [ AnnotationMemberList() ]
+ "@" ClassName() [ AnnotationMemberList() ]
}
void AnnotationMemberList():
@@ -2557,6 +2557,13 @@ String VoidName() #void:
{return s.toString();}
}
+// Produces a ClassOrInterfaceType, possibly with an ambiguous LHS
+void ClassName() #void:
+{}
+{
+ AmbiguousName() { forceTypeContext(); }
+}
+
// This is used to get JJTree to generate a node.
// Variable references are always ambiguous
// when they're parsed, so they're not created
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAmbiguousName.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAmbiguousName.java
index 7272ccdd4b..ff5fa4d87c 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAmbiguousName.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAmbiguousName.java
@@ -173,14 +173,10 @@ public final class ASTAmbiguousName extends AbstractJavaExpr implements ASTRefer
}
/**
- * Delete this name from the children of the parent. The image of
- * this name is prepended to the image of the parent.
+ * Delete this name from the children of the parent.
*/
- void deleteInParentPrependImage(char delim) {
+ void deleteInParent() {
AbstractJavaNode parent = (AbstractJavaNode) getParent();
- String image = parent.getImage();
- parent.setImage(getName() + delim + image);
-
parent.removeChildAtIndex(this.getIndexInParent());
}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotation.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotation.java
index 677f395991..498e55b516 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotation.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotation.java
@@ -9,45 +9,47 @@ import java.util.Iterator;
import org.checkerframework.checker.nullness.qual.Nullable;
import net.sourceforge.pmd.lang.ast.NodeStream;
-import net.sourceforge.pmd.util.StringUtil;
+import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
/**
* Represents an annotation.
*
*
*
- * Annotation ::= "@" Name {@link ASTAnnotationMemberList AnnotationMemberList}?
+ * Annotation ::= "@" {@link ASTClassOrInterfaceType ClassName} {@link ASTAnnotationMemberList AnnotationMemberList}?
*
*
*/
public final class ASTAnnotation extends AbstractJavaTypeNode implements TypeNode, ASTMemberValue, Iterable {
- String name;
-
ASTAnnotation(int id) {
super(id);
}
/**
- * Returns the name of the annotation as it is used,
- * eg {@code java.lang.Override} or {@code Override}.
+ * Returns the node that represents the name of the annotation.
*/
- public String getAnnotationName() {
- return name;
+ public ASTClassOrInterfaceType getTypeNode() {
+ return (ASTClassOrInterfaceType) getChild(0);
}
- @Override
- @Deprecated
- public String getImage() {
- return name;
+ /**
+ * Returns the symbol of the annotation type.
+ */
+ public JClassSymbol getSymbol() {
+ // This cast would fail if you use a type parameter as an
+ // annotation name. This is reported as an error by the
+ // disambiguation pass
+ return (JClassSymbol) getTypeNode().getReferencedSym();
}
+
/**
* Returns the simple name of the annotation.
*/
public String getSimpleName() {
- return StringUtil.substringAfterLast(getImage(), '.');
+ return getTypeNode().getSimpleName();
}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTCatchParameter.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTCatchParameter.java
index f8d46c21a5..f74529dc9e 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTCatchParameter.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTCatchParameter.java
@@ -6,6 +6,8 @@ package net.sourceforge.pmd.lang.java.ast;
import org.checkerframework.checker.nullness.qual.NonNull;
+import net.sourceforge.pmd.lang.ast.NodeStream;
+
/**
* Formal parameter of a {@linkplain ASTCatchClause catch clause}
@@ -50,7 +52,7 @@ public final class ASTCatchParameter extends AbstractJavaNode
/** Returns the name of this parameter. */
public String getName() {
- return getVarId().getVariableName();
+ return getVarId().getName();
}
@@ -59,7 +61,21 @@ public final class ASTCatchParameter extends AbstractJavaNode
* {@link ASTUnionType UnionType}.
*/
public ASTType getTypeNode() {
- return children(ASTType.class).first();
+ return (ASTType) getChild(1);
+ }
+
+
+ /**
+ * Returns a stream of all declared exception types (expanding a union
+ * type if present).
+ */
+ public NodeStream getAllExceptionTypes() {
+ ASTType typeNode = getTypeNode();
+ if (typeNode instanceof ASTUnionType) {
+ return typeNode.children(ASTClassOrInterfaceType.class);
+ } else {
+ return NodeStream.of((ASTClassOrInterfaceType) typeNode);
+ }
}
}
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 1dfe4e6b48..ade5b6330d 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
@@ -7,6 +7,7 @@ package net.sourceforge.pmd.lang.java.ast;
import org.checkerframework.checker.nullness.qual.Nullable;
import net.sourceforge.pmd.annotation.Experimental;
+import net.sourceforge.pmd.internal.util.AssertionUtil;
import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken;
import net.sourceforge.pmd.lang.java.symbols.JTypeDeclSymbol;
@@ -31,31 +32,44 @@ import net.sourceforge.pmd.lang.java.symbols.JTypeDeclSymbol;
*/
// @formatter:on
public final class ASTClassOrInterfaceType extends AbstractJavaTypeNode implements ASTReferenceType {
+ // todo rename to ASTClassType
private JTypeDeclSymbol symbol;
- ASTClassOrInterfaceType(ASTAmbiguousName lhs, String image) {
+ private String simpleName;
+
+ // Note that this is only populated during disambiguation, if
+ // the ambiguous qualifier is resolved to a package name
+ private boolean isFqcn;
+
+ ASTClassOrInterfaceType(ASTAmbiguousName lhs, String simpleName) {
super(JavaParserImplTreeConstants.JJTCLASSORINTERFACETYPE);
+ assert lhs != null : "Null LHS";
+
this.addChild(lhs, 0);
- this.setImage(image);
+ this.simpleName = simpleName;
+ assertSimpleNameOk();
}
ASTClassOrInterfaceType(ASTAmbiguousName simpleName) {
super(JavaParserImplTreeConstants.JJTCLASSORINTERFACETYPE);
- this.setImage(simpleName.getImage());
- }
+ this.simpleName = simpleName.getName();
+ assertSimpleNameOk();
+ }
// Just for one usage in Symbol table
+ @Deprecated
public ASTClassOrInterfaceType(String simpleName) {
super(JavaParserImplTreeConstants.JJTCLASSORINTERFACETYPE);
- this.setImage(simpleName);
+ this.simpleName = simpleName;
}
- ASTClassOrInterfaceType(ASTClassOrInterfaceType lhs, String image, JavaccToken firstToken, JavaccToken identifier) {
+ ASTClassOrInterfaceType(@Nullable ASTClassOrInterfaceType lhs, boolean isFqcn, JavaccToken firstToken, JavaccToken identifier) {
super(JavaParserImplTreeConstants.JJTCLASSORINTERFACETYPE);
- this.setImage(image);
+ this.setImage(identifier.getImage());
+ this.isFqcn = isFqcn;
if (lhs != null) {
this.addChild(lhs, 0);
}
@@ -68,6 +82,35 @@ public final class ASTClassOrInterfaceType extends AbstractJavaTypeNode implemen
super(id);
}
+ @Override
+ protected void setImage(String image) {
+ this.simpleName = image;
+ assertSimpleNameOk();
+ }
+
+ @Deprecated
+ @Override
+ public String getImage() {
+ return null;
+ }
+
+ private void assertSimpleNameOk() {
+ assert this.simpleName != null
+ && this.simpleName.indexOf('.') < 0
+ && AssertionUtil.isJavaIdentifier(this.simpleName)
+ : "Invalid simple name '" + this.simpleName + "'";
+ }
+
+ /**
+ * Returns true if the type was written with a full package qualification.
+ * For example, {@code java.lang.Override}. For nested types, only the
+ * leftmost type is considered fully qualified. Eg in {@code p.Outer.Inner},
+ * this method will return true for the type corresponding to {@code p.Outer},
+ * but false for the enclosing {@code p.Outer.Inner}.
+ */
+ public boolean isFullyQualified() {
+ return isFqcn;
+ }
void setSymbol(JTypeDeclSymbol symbol) {
this.symbol = symbol;
@@ -109,10 +152,11 @@ public final class ASTClassOrInterfaceType extends AbstractJavaTypeNode implemen
/**
- * Returns the simple name of this type.
+ * Returns the simple name of this type. Use the {@linkplain #getReferencedSym() symbol}
+ * to get more information.
*/
public String getSimpleName() {
- return AstImplUtil.getLastSegment(getImage(), '.');
+ return simpleName;
}
/**
@@ -125,11 +169,15 @@ public final class ASTClassOrInterfaceType extends AbstractJavaTypeNode implemen
/**
* For now this returns the name of the type with all the segments,
* without annotations or type parameters.
+ *
+ * @deprecated This is useless and won't be implemented. We can use the
+ * symbol, or better the upcoming type API to pretty print the type.
*/
@Override
@Experimental
+ @Deprecated
public String getTypeImage() {
- return children(ASTType.class).firstOpt().map(s -> s.getTypeImage() + ".").orElse("") + getImage();
+ return children(ASTType.class).firstOpt().map(s -> s.getTypeImage() + ".").orElse("") + getSimpleName();
}
/**
@@ -139,7 +187,10 @@ public final class ASTClassOrInterfaceType extends AbstractJavaTypeNode implemen
*
* @return {@code true} if this node referencing a type in the same
* compilation unit, {@code false} otherwise.
+ *
+ * @deprecated This may be removed once type resolution is afoot
*/
+ @Deprecated
public boolean isReferenceToClassSameCompilationUnit() {
ASTCompilationUnit root = getFirstParentOfType(ASTCompilationUnit.class);
for (ASTClassOrInterfaceDeclaration c : root.findDescendantsOfType(ASTClassOrInterfaceDeclaration.class, true)) {
@@ -155,9 +206,7 @@ public final class ASTClassOrInterfaceType extends AbstractJavaTypeNode implemen
return false;
}
-
- public boolean isAnonymousClass() {
- return getParent().getFirstChildOfType(ASTClassOrInterfaceBody.class) != null;
+ void setFullyQualified() {
+ this.isFqcn = true;
}
-
}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTList.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTList.java
index b36f609b79..953b09060b 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTList.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTList.java
@@ -99,6 +99,10 @@ public abstract class ASTList extends AbstractJavaNode imple
return list == null ? NodeStream.empty() : list.toStream();
}
+ public static int sizeOrZero(@Nullable ASTList> list) {
+ return list == null ? 0 : list.size();
+ }
+
/**
* Super type for *nonempty* lists that *only* have nodes of type {@code }
* as a child.
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTThrowStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTThrowStatement.java
index 0940a98298..150a8e8840 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTThrowStatement.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTThrowStatement.java
@@ -32,23 +32,4 @@ public final class ASTThrowStatement extends AbstractStatement implements ASTSwi
return (ASTExpression) getFirstChild();
}
- /**
- * Gets the image of the first ASTClassOrInterfaceType child or
- * null
if none is found. Note that when the statement is
- * something like throw new Exception, this method returns 'Exception' and
- * if the throw statement is like throw e: this method returns 'e'. A
- * special case of returning null
is when the throws is like
- * throw this.e or throw this.
- *
- * This is too specific
- *
- * TODO - use symbol table (?)
- *
- * @return the image of the first ASTClassOrInterfaceType node found or
- * null
- */
- public String getFirstClassOrInterfaceTypeImage() {
- final ASTClassOrInterfaceType t = getFirstDescendantOfType(ASTClassOrInterfaceType.class);
- return t == null ? null : t.getImage();
- }
}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTryStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTryStatement.java
index 759dc6ba3d..e385c9a809 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTryStatement.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTryStatement.java
@@ -4,10 +4,10 @@
package net.sourceforge.pmd.lang.java.ast;
-import java.util.List;
-
import org.checkerframework.checker.nullness.qual.Nullable;
+import net.sourceforge.pmd.lang.ast.NodeStream;
+
/**
* Try statement node.
@@ -64,8 +64,8 @@ public final class ASTTryStatement extends AbstractStatement {
* Returns the catch statement nodes of this try statement.
* If there are none, returns an empty list.
*/
- public List getCatchClauses() {
- return findChildrenOfType(ASTCatchClause.class);
+ public NodeStream getCatchClauses() {
+ return children(ASTCatchClause.class);
}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AstDisambiguationPass.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AstDisambiguationPass.java
index dc50fb70a9..9c3d954198 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AstDisambiguationPass.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AstDisambiguationPass.java
@@ -28,6 +28,7 @@ import net.sourceforge.pmd.lang.java.symbols.JVariableSymbol;
import net.sourceforge.pmd.lang.java.symbols.table.JSymbolTable;
import net.sourceforge.pmd.lang.java.symbols.table.internal.JavaResolvers;
import net.sourceforge.pmd.lang.java.types.JClassType;
+import net.sourceforge.pmd.lang.java.symbols.table.internal.SemanticChecksLogger;
/**
* This implements name disambiguation following JLS§6.5.2.
@@ -294,6 +295,7 @@ final class AstDisambiguationPass {
visitChildren(type, ctx);
if (type.getReferencedSym() != null) {
+ postProcess(type, ctx.processor);
return null;
}
@@ -319,9 +321,27 @@ final class AstDisambiguationPass {
}
assert type.getReferencedSym() != null : "Null symbol for " + type;
+
+ postProcess(type, processor);
return null;
}
+ private void postProcess(ASTClassOrInterfaceType type, JavaAstProcessor processor) {
+ JTypeDeclSymbol sym = type.getReferencedSym();
+ if (type.getParent() instanceof ASTAnnotation) {
+ if (!(sym instanceof JClassSymbol && (sym.isUnresolved() || ((JClassSymbol) sym).isAnnotation()))) {
+ processor.getLogger().error(type, SemanticChecksLogger.EXPECTED_ANNOTATION_TYPE);
+ }
+ return;
+ }
+
+ int actualArity = ASTList.sizeOrZero(type.getTypeArguments());
+ int expectedArity = sym instanceof JClassSymbol ? ((JClassSymbol) sym).getTypeParameterCount() : 0;
+ if (actualArity != 0 && actualArity != expectedArity) {
+ processor.getLogger().error(type, SemanticChecksLogger.MALFORMED_GENERIC_TYPE, expectedArity, actualArity);
+ }
+ }
+
@NonNull
private JTypeDeclSymbol setArity(ASTClassOrInterfaceType type, JavaAstProcessor processor, String canonicalName) {
int arity = ASTList.orEmpty(type.getTypeArguments()).size();
@@ -373,11 +393,11 @@ final class AstDisambiguationPass {
JTypeDeclSymbol typeResult = resolveSingleTypeName(symTable, firstIdent.getImage(), ctx, name);
if (typeResult != null) {
- return resolveType(null, typeResult, firstIdent.getImage(), firstIdent, tokens, name, isPackageOrTypeOnly, ctx);
+ return resolveType(null, typeResult, false, firstIdent, tokens, name, isPackageOrTypeOnly, ctx);
}
// otherwise, first is reclassified as package name.
- return resolvePackage(firstIdent, firstIdent.getImage(), tokens, name, isPackageOrTypeOnly, ctx);
+ return resolvePackage(firstIdent, new StringBuilder(firstIdent.getImage()), tokens, name, isPackageOrTypeOnly, ctx);
}
private static JTypeDeclSymbol resolveSingleTypeName(JSymbolTable symTable, String image, ReferenceCtx ctx, JavaNode errorLoc) {
@@ -436,7 +456,7 @@ final class AstDisambiguationPass {
*/
private static ASTExpression resolveType(final @Nullable ASTClassOrInterfaceType qualifier, // lhs
final JTypeDeclSymbol sym, // symbol for the type
- final String image, // image of the new ClassType, possibly with a prepended package name
+ final boolean isFqcn, // whether this is a fully-qualified name
final JavaccToken identifier, // ident of the simple name of the symbol
final Iterator remaining, // rest of tokens, starting with following '.'
final ASTAmbiguousName ambig, // original ambiguous name
@@ -446,7 +466,7 @@ final class AstDisambiguationPass {
TokenUtils.expectKind(identifier, JavaTokenKinds.IDENTIFIER);
- final ASTClassOrInterfaceType type = new ASTClassOrInterfaceType(qualifier, image, ambig.getFirstToken(), identifier);
+ final ASTClassOrInterfaceType type = new ASTClassOrInterfaceType(qualifier, isFqcn, ambig.getFirstToken(), identifier);
type.setSymbol(sym);
if (!remaining.hasNext()) { // done
@@ -474,7 +494,7 @@ final class AstDisambiguationPass {
}
if (inner != null) {
- return resolveType(type, inner, nextSimpleName, nextIdent, remaining, ambig, isPackageOrTypeOnly, ctx);
+ return resolveType(type, inner, false, nextIdent, remaining, ambig, isPackageOrTypeOnly, ctx);
}
// no inner type, yet we have a lhs that is a type...
@@ -500,7 +520,7 @@ final class AstDisambiguationPass {
* class, then we report it and return the original ambiguous name.
*/
private static ASTExpression resolvePackage(JavaccToken identifier,
- String packageImage,
+ StringBuilder packageImage,
Iterator remaining,
ASTAmbiguousName ambig,
boolean isPackageOrTypeOnly,
@@ -526,16 +546,17 @@ final class AstDisambiguationPass {
JavaccToken nextIdent = skipToNextIdent(remaining);
- String canonical = packageImage + '.' + nextIdent.getImage();
+ packageImage.append('.').append(nextIdent.getImage());
+ String canonical = packageImage.toString();
// Don't interpret periods as nested class separators (this will be handled by resolveType).
// Otherwise lookup of a fully qualified name would be quadratic
JClassSymbol nextClass = processor.getSymResolver().resolveClassFromBinaryName(canonical);
if (nextClass != null) {
- return resolveType(null, nextClass, canonical, nextIdent, remaining, ambig, isPackageOrTypeOnly, ctx);
+ return resolveType(null, nextClass, true, nextIdent, remaining, ambig, isPackageOrTypeOnly, ctx);
} else {
- return resolvePackage(nextIdent, canonical, remaining, ambig, isPackageOrTypeOnly, ctx);
+ return resolvePackage(nextIdent, packageImage, remaining, ambig, isPackageOrTypeOnly, ctx);
}
}
@@ -543,17 +564,19 @@ final class AstDisambiguationPass {
* Force resolution of the ambiguous name as a package name.
* The parent type's image is set to a package name + simple name.
*/
- private static void forceResolveAsFullPackageNameOfParent(String packageImage, ASTAmbiguousName ambig, JavaAstProcessor processor) {
+ private static void forceResolveAsFullPackageNameOfParent(StringBuilder packageImage, ASTAmbiguousName ambig, JavaAstProcessor processor) {
ASTClassOrInterfaceType parent = (ASTClassOrInterfaceType) ambig.getParent();
- String fullName = packageImage + '.' + parent.getSimpleName();
+ packageImage.append('.').append(parent.getSimpleName());
+ String fullName = packageImage.toString();
JClassSymbol parentClass = processor.getSymResolver().resolveClassFromCanonicalName(fullName);
if (parentClass == null) {
processor.getLogger().warning(parent, CANNOT_RESOLVE_AMBIGUOUS_NAME, fullName, Fallback.TYPE);
parentClass = processor.makeUnresolvedReference(fullName);
}
parent.setSymbol(parentClass);
- ambig.deleteInParentPrependImage('.');
+ parent.setFullyQualified();
+ ambig.deleteInParent();
}
private static JavaccToken skipToNextIdent(Iterator remaining) {
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/internal/JavaDesignerBindings.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/internal/JavaDesignerBindings.java
index a6e82f04cf..195d8e6c5c 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/internal/JavaDesignerBindings.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/internal/JavaDesignerBindings.java
@@ -11,7 +11,9 @@ import java.util.Collections;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.ast.xpath.Attribute;
+import net.sourceforge.pmd.lang.java.ast.ASTAnnotation;
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTInfixExpression;
@@ -41,7 +43,7 @@ public final class JavaDesignerBindings extends DefaultDesignerBindings {
@Override
public Attribute getMainAttribute(Node node) {
if (node instanceof JavaNode) {
- Attribute attr = ((JavaNode) node).acceptVisitor(MainAttrVisitor.INSTANCE, null);
+ Attribute attr = node.acceptVisitor(MainAttrVisitor.INSTANCE, null);
if (attr != null) {
return attr;
}
@@ -135,9 +137,24 @@ public final class JavaDesignerBindings extends DefaultDesignerBindings {
return new Attribute(node, "SimpleName", node.getSimpleName());
}
+ @Override
+ public Attribute visit(ASTAnnotation node, Void data) {
+ return new Attribute(node, "SimpleName", node.getSimpleName());
+ }
+
+ @Override
+ public Attribute visit(ASTClassOrInterfaceType node, Void data) {
+ return new Attribute(node, "SimpleName", node.getSimpleName());
+ }
+
@Override
public Attribute visit(ASTMethodDeclaration node, Void data) {
return new Attribute(node, "Name", node.getName());
}
+
+ @Override
+ public Attribute visit(ASTVariableDeclaratorId node, Void data) {
+ return new Attribute(node, "Name", node.getName());
+ }
}
}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/IdenticalCatchBranchesRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/IdenticalCatchBranchesRule.java
index d912a4be07..a86a433dd0 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/IdenticalCatchBranchesRule.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/IdenticalCatchBranchesRule.java
@@ -77,7 +77,7 @@ public class IdenticalCatchBranchesRule extends AbstractJavaRule {
@Override
public Object visit(ASTTryStatement node, Object data) {
- List catchStatements = node.getCatchClauses();
+ List catchStatements = node.getCatchClauses().toList();
Set> equivClasses = equivalenceClasses(catchStatements);
for (List identicalStmts : equivClasses) {
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExceptionAsFlowControlRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExceptionAsFlowControlRule.java
index db771f0b1f..578f86f802 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExceptionAsFlowControlRule.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExceptionAsFlowControlRule.java
@@ -4,14 +4,15 @@
package net.sourceforge.pmd.lang.java.rule.design;
-import java.util.List;
-
+import net.sourceforge.pmd.lang.ast.NodeStream;
import net.sourceforge.pmd.lang.java.ast.ASTCatchClause;
+import net.sourceforge.pmd.lang.java.ast.ASTCatchParameter;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
-import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
+import net.sourceforge.pmd.lang.java.ast.ASTConstructorCall;
+import net.sourceforge.pmd.lang.java.ast.ASTExpression;
import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement;
import net.sourceforge.pmd.lang.java.ast.ASTTryStatement;
-import net.sourceforge.pmd.lang.java.ast.ASTType;
+import net.sourceforge.pmd.lang.java.ast.JavaNode;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
/**
@@ -23,24 +24,32 @@ public class ExceptionAsFlowControlRule extends AbstractJavaRule {
@Override
public Object visit(ASTThrowStatement node, Object data) {
- ASTTryStatement parent = node.getFirstParentOfType(ASTTryStatement.class);
- if (parent == null) {
+ JavaNode firstTryOrCatch = node.ancestors().map(NodeStream.asInstanceOf(ASTTryStatement.class, ASTCatchClause.class)).first();
+ NodeStream enclosingTries = node.ancestors(ASTTryStatement.class);
+ if (firstTryOrCatch instanceof ASTCatchClause) {
+ // if the exception is thrown in a catch block, then the
+ // first try we're looking for is the next one
+ enclosingTries = enclosingTries.drop(1);
+ }
+ if (enclosingTries.isEmpty()) {
return data;
}
- for (parent = parent.getFirstParentOfType(ASTTryStatement.class); parent != null; parent = parent
- .getFirstParentOfType(ASTTryStatement.class)) {
- List list = parent.findDescendantsOfType(ASTCatchClause.class);
- for (ASTCatchClause catchStmt : list) {
- ASTFormalParameter fp = (ASTFormalParameter) catchStmt.getChild(0);
- ASTType type = fp.getFirstDescendantOfType(ASTType.class);
- ASTClassOrInterfaceType name = type.getFirstDescendantOfType(ASTClassOrInterfaceType.class);
- if (node.getFirstClassOrInterfaceTypeImage() != null
- && node.getFirstClassOrInterfaceTypeImage().equals(name.getImage())) {
- addViolation(data, name);
- }
- }
+ ASTExpression expr = node.getExpr();
+ ASTClassOrInterfaceType thrownType;
+ if (expr instanceof ASTConstructorCall) {
+ // todo when typeres is up we can just use the static type of the expression
+ thrownType = ((ASTConstructorCall) expr).getTypeNode();
+ } else {
+ return data;
}
+
+ enclosingTries.flatMap(ASTTryStatement::getCatchClauses)
+ .map(ASTCatchClause::getParameter)
+ .flatMap(ASTCatchParameter::getAllExceptionTypes)
+ // todo when type res is up: use a subtyping test
+ .filter(ex -> ex.getReferencedSym().equals(thrownType.getReferencedSym()))
+ .forEach(ex -> addViolation(data, ex));
return data;
}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/AnnotationSuppressionUtil.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/AnnotationSuppressionUtil.java
index f66ad8b77d..9a0f9397b5 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/AnnotationSuppressionUtil.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/AnnotationSuppressionUtil.java
@@ -107,18 +107,15 @@ final class AnnotationSuppressionUtil {
// @formatter:on
private static boolean annotationSuppresses(ASTAnnotation annotation, Rule rule) {
- // if (TypeHelper.symbolEquals(annotation.getTypeMirror(), SuppressWarnings.class)) { // fixme annot name disambiguation
- if ("SuppressWarnings".equals(annotation.getSimpleName())) {
-
- for (ASTStringLiteral lit : annotation.getValuesForName(ASTMemberValuePair.VALUE_ATTR)
- .filterIs(ASTStringLiteral.class)) {
- String value = lit.getConstValue();
- if ("PMD".equals(value)
- || ("PMD." + rule.getName()).equals(value)
- || "all".equals(value)
- || "serial".equals(value) && SERIAL_RULES.contains(rule.getName())
- || "unused".equals(value) && UNUSED_RULES.contains(rule.getName())
- ) {
+ if (annotation.getSymbol().getBinaryName().equals("java.lang.SuppressWarnings")) {
+ for (ASTStringLiteral element : annotation.findDescendantsOfType(ASTStringLiteral.class)) {
+ if (element.hasImageEqualTo("\"PMD\"") || element.hasImageEqualTo(
+ "\"PMD." + rule.getName() + "\"")
+ // Check for standard annotations values
+ || element.hasImageEqualTo("\"all\"")
+ || element.hasImageEqualTo("\"serial\"") && SERIAL_RULES.contains(rule.getName())
+ || element.hasImageEqualTo("\"unused\"") && UNUSED_RULES.contains(rule.getName())
+ || element.hasImageEqualTo("\"all\"")) {
return true;
}
}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/table/internal/SemanticChecksLogger.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/table/internal/SemanticChecksLogger.java
index 9b5e935a4e..03ab8dad22 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/table/internal/SemanticChecksLogger.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/table/internal/SemanticChecksLogger.java
@@ -50,6 +50,9 @@ public interface SemanticChecksLogger {
*/
String MALFORMED_GENERIC_TYPE = "Maformed generic type: expected {0} type arguments, got {1}";
+ // this is an error
+ String EXPECTED_ANNOTATION_TYPE = "Expected an annotation type";
+
/**
* An ambiguous name is completely ambiguous. We don't have info
* about it at all, classpath is incomplete or code is incorrect.
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/TypeHelper.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/TypeHelper.java
index a0a45643f5..d77df543c8 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/TypeHelper.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/TypeHelper.java
@@ -8,19 +8,20 @@ import static net.sourceforge.pmd.util.CollectionUtil.any;
import java.lang.reflect.Array;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
+import net.sourceforge.pmd.lang.java.ast.ASTAnnotation;
import net.sourceforge.pmd.lang.java.ast.ASTAnnotationTypeDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
-import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration;
import net.sourceforge.pmd.lang.java.ast.TypeNode;
+import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
import net.sourceforge.pmd.lang.java.symbols.JTypeDeclSymbol;
import net.sourceforge.pmd.lang.java.symboltable.TypedNameDeclaration;
import net.sourceforge.pmd.lang.java.typeresolution.internal.NullableClassLoader;
@@ -102,21 +103,21 @@ public final class TypeHelper {
|| clazzName.equals(n.getName());
}
- private static boolean fallbackIsA(TypeNode n, String clazzName) {
- if (n.getImage() != null && !n.getImage().contains(".") && clazzName.contains(".")) {
- // simple name detected, check the imports to get the full name and use that for fallback
- List imports = n.getRoot().findChildrenOfType(ASTImportDeclaration.class);
- for (ASTImportDeclaration importDecl : imports) {
- if (n.hasImageEqualTo(importDecl.getImportedSimpleName())) {
- // found the import, compare the full names
- return clazzName.equals(importDecl.getImportedName());
- }
- }
- }
+ private static boolean fallbackIsA(final TypeNode n, String clazzName) {
+ // Later we won't need a fallback. Symbols already contain subclass information.
- // fall back on using the simple name of the class only
- if (clazzName.equals(n.getImage()) || clazzName.endsWith("." + n.getImage())) {
+ if (n instanceof ASTAnyTypeDeclaration && ((ASTAnyTypeDeclaration) n).getBinaryName().equals(clazzName)) {
return true;
+ } else if (n instanceof ASTClassOrInterfaceType || n instanceof ASTAnnotation) {
+ ASTClassOrInterfaceType classType;
+ if (n instanceof ASTAnnotation) {
+ classType = ((ASTAnnotation) n).getTypeNode();
+ } else {
+ classType = (ASTClassOrInterfaceType) n;
+ }
+
+ JTypeDeclSymbol sym = classType.getReferencedSym();
+ return sym instanceof JClassSymbol && ((JClassSymbol) sym).getBinaryName().equals(clazzName);
}
if (n instanceof ASTClassOrInterfaceDeclaration) {
diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTThrowStatementTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTThrowStatementTest.java
deleted file mode 100644
index c31912118a..0000000000
--- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTThrowStatementTest.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
- * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
- */
-
-package net.sourceforge.pmd.lang.java.ast;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
-import org.junit.Test;
-
-/**
- * Created on Jan 19, 2005
- * @author mgriffa
- */
-public class ASTThrowStatementTest extends BaseParserTest {
-
- @Test
- public final void testGetFirstASTNameImageNull() {
- ASTThrowStatement t = java.getNodes(ASTThrowStatement.class, NULL_NAME).get(0);
- assertNull(t.getFirstClassOrInterfaceTypeImage());
- }
-
- @Test
- public final void testGetFirstASTNameImageNew() {
- ASTThrowStatement t = java.getNodes(ASTThrowStatement.class, OK_NAME).get(0);
- assertEquals("FooException", t.getFirstClassOrInterfaceTypeImage());
- }
-
- private static final String NULL_NAME = "public class Test {\n void bar() {\n throw e;\n }\n}";
-
- private static final String OK_NAME = "public class Test {\n void bar() {\n throw new FooException();\n }\n}";
-}
diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorIdTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorIdTest.java
index d0230b626b..ed393898dd 100644
--- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorIdTest.java
+++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorIdTest.java
@@ -26,7 +26,7 @@ public class ASTVariableDeclaratorIdTest extends BaseParserTest {
ASTVariableDeclaratorId id = acu.findDescendantsOfType(ASTVariableDeclaratorId.class).get(0);
ASTClassOrInterfaceType name = (ASTClassOrInterfaceType) id.getTypeNameNode();
- assertEquals("String", name.getImage());
+ assertEquals("String", name.getSimpleName());
}
@Test
@@ -35,7 +35,7 @@ public class ASTVariableDeclaratorIdTest extends BaseParserTest {
ASTVariableDeclaratorId id = acu.findDescendantsOfType(ASTVariableDeclaratorId.class).get(0);
ASTClassOrInterfaceType name = (ASTClassOrInterfaceType) id.getTypeNode();
- assertEquals("String", name.getImage());
+ assertEquals("String", name.getSimpleName());
}
@Test
diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/XPathRuleTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/XPathRuleTest.java
index 66e096b052..64f2bb3d66 100644
--- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/XPathRuleTest.java
+++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/XPathRuleTest.java
@@ -30,6 +30,7 @@ import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.JavaLanguageModule;
import net.sourceforge.pmd.lang.java.JavaParsingHelper;
import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
+import net.sourceforge.pmd.lang.java.ast.JavaNode;
import net.sourceforge.pmd.lang.rule.XPathRule;
import net.sourceforge.pmd.lang.rule.xpath.JaxenXPathRuleQuery;
import net.sourceforge.pmd.lang.rule.xpath.SaxonXPathRuleQuery;
@@ -182,8 +183,8 @@ public class XPathRuleTest extends RuleTst {
xpathRuleQuery.setVersion(XPathRuleQuery.XPATH_1_0);
List nodes = xpathRuleQuery.evaluate(cu, ruleContext);
assertEquals(2, nodes.size());
- assertEquals("Bar", nodes.get(0).getImage());
- assertEquals("Baz", nodes.get(1).getImage());
+ assertEquals("Bar", ((JavaNode) nodes.get(0)).getText().toString());
+ assertEquals("Baz", ((JavaNode) nodes.get(1)).getText().toString());
// XPATH version 2.0
xpathRuleQuery = new SaxonXPathRuleQuery();
@@ -192,8 +193,8 @@ public class XPathRuleTest extends RuleTst {
xpathRuleQuery.setVersion(XPathRuleQuery.XPATH_2_0);
nodes = xpathRuleQuery.evaluate(cu, ruleContext);
assertEquals(2, nodes.size());
- assertEquals("Bar", nodes.get(0).getImage());
- assertEquals("Baz", nodes.get(1).getImage());
+ assertEquals("Bar", ((JavaNode) nodes.get(0)).getText().toString());
+ assertEquals("Baz", ((JavaNode) nodes.get(1)).getText().toString());
}
private static Report getReportForTestString(Rule r, String test) throws PMDException {
diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/typeresolution/TypeHelperTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/typeresolution/TypeHelperTest.java
index 5142b05b96..fae0d56ac5 100644
--- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/typeresolution/TypeHelperTest.java
+++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/typeresolution/TypeHelperTest.java
@@ -93,8 +93,7 @@ public class TypeHelperTest extends BaseNonParserTest {
Assert.assertNull(annotation.getType());
Assert.assertTrue(TypeHelper.isA(annotation, "foo.Stuff"));
Assert.assertFalse(TypeHelper.isA(annotation, "other.Stuff"));
- // if the searched class name is not fully qualified, then the search should still be successfull
- Assert.assertTrue(TypeHelper.isA(annotation, "Stuff"));
+ Assert.assertFalse(TypeHelper.isA(annotation, "Stuff"));
}
private void assertIsA(TypeNode node, Class> type) {
diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTAnnotationTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTAnnotationTest.kt
index 1a9685cd76..b1a0c8741f 100644
--- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTAnnotationTest.kt
+++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTAnnotationTest.kt
@@ -31,18 +31,20 @@ class ASTAnnotationTest : ParserTestSpec({
"@F" should parseAs {
child {
- it::getAnnotationName shouldBe "F"
it::getSimpleName shouldBe "F"
+ it::getTypeNode shouldBe classType("F")
+
it::getMemberList shouldBe null
}
}
"@java.lang.Override" should parseAs {
child {
- it::getAnnotationName shouldBe "java.lang.Override"
it::getSimpleName shouldBe "Override"
+ it::getTypeNode shouldBe qualClassType("java.lang.Override")
+
it::getMemberList shouldBe null
}
}
@@ -56,9 +58,10 @@ class ASTAnnotationTest : ParserTestSpec({
"@F(\"ohio\")" should parseAs {
child {
- it::getAnnotationName shouldBe "F"
it::getSimpleName shouldBe "F"
+ it::getTypeNode shouldBe classType("F")
+
it::getMemberList shouldBe child {
shorthandMemberValue {
stringLit("\"ohio\"")
@@ -69,9 +72,10 @@ class ASTAnnotationTest : ParserTestSpec({
"@org.F({java.lang.Math.PI})" should parseAs {
child {
- it::getAnnotationName shouldBe "org.F"
it::getSimpleName shouldBe "F"
+ it::getTypeNode shouldBe qualClassType("org.F")
+
it::getMemberList shouldBe child {
shorthandMemberValue {
child {
@@ -87,9 +91,9 @@ class ASTAnnotationTest : ParserTestSpec({
"@org.F({@Aha, @Oh})" should parseAs {
child {
- it::getAnnotationName shouldBe "org.F"
it::getSimpleName shouldBe "F"
+ it::getTypeNode shouldBe qualClassType("org.F")
it::getMemberList shouldBe child {
shorthandMemberValue {
@@ -103,9 +107,9 @@ class ASTAnnotationTest : ParserTestSpec({
}
"@org.F(@Oh)" should parseAs {
child {
- it::getAnnotationName shouldBe "org.F"
it::getSimpleName shouldBe "F"
+ it::getTypeNode shouldBe qualClassType("org.F")
it::getMemberList shouldBe child {
shorthandMemberValue {
@@ -124,9 +128,9 @@ class ASTAnnotationTest : ParserTestSpec({
"@F(a=\"ohio\")" should parseAs {
child {
- it::getAnnotationName shouldBe "F"
it::getSimpleName shouldBe "F"
+ it::getTypeNode shouldBe classType("F")
it::getMemberList shouldBe child {
memberValuePair("a") {
@@ -138,9 +142,9 @@ class ASTAnnotationTest : ParserTestSpec({
"@org.F(a={java.lang.Math.PI}, b=2)" should parseAs {
child {
- it::getAnnotationName shouldBe "org.F"
it::getSimpleName shouldBe "F"
+ it::getTypeNode shouldBe qualClassType("org.F")
it::getMemberList shouldBe child {
memberValuePair("a") {
@@ -167,6 +171,8 @@ class ASTAnnotationTest : ParserTestSpec({
child {
+ it::getTypeNode shouldBe classType("TestAnnotation")
+
it::getMemberList shouldBe child {
shorthandMemberValue {
@@ -174,6 +180,8 @@ class ASTAnnotationTest : ParserTestSpec({
child {
annotation {
+ it::getTypeNode shouldBe classType("SuppressWarnings")
+
it::getMemberList shouldBe child {
shorthandMemberValue {
child {}
@@ -181,6 +189,9 @@ class ASTAnnotationTest : ParserTestSpec({
}
}
annotation {
+
+ it::getTypeNode shouldBe classType("SuppressWarnings")
+
it::getMemberList shouldBe child {
memberValuePair("value") {
it::isShorthand shouldBe false
@@ -191,6 +202,9 @@ class ASTAnnotationTest : ParserTestSpec({
}
}
annotation {
+
+ it::getTypeNode shouldBe classType("SuppressWarnings")
+
it::getMemberList shouldBe child {
shorthandMemberValue {
child {
diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTConstructorCallTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTConstructorCallTest.kt
index 07a6b9a757..c274f33d85 100644
--- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTConstructorCallTest.kt
+++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTConstructorCallTest.kt
@@ -12,12 +12,9 @@ class ASTConstructorCallTest : ParserTestSpec({
constructorCall {
it::isQualifiedInstanceCreation shouldBe false
- it::getTypeNode shouldBe child {
- it::getTypeImage shouldBe "Foo"
- }
-
- it::getArguments shouldBe child {
+ it::getTypeNode shouldBe classType("Foo")
+ it::getArguments shouldBe argList(1) {
variableAccess("a")
}
}
@@ -31,9 +28,7 @@ class ASTConstructorCallTest : ParserTestSpec({
unspecifiedChild()
}
- it::getTypeNode shouldBe child {
- it::getTypeImage shouldBe "Foo"
-
+ it::getTypeNode shouldBe classType("Foo") {
it::getTypeArguments shouldBe child {
unspecifiedChild()
}
@@ -49,8 +44,7 @@ class ASTConstructorCallTest : ParserTestSpec({
it::getExplicitTypeArguments shouldBe null
- it::getTypeNode shouldBe child {
- it::getTypeImage shouldBe "Foo"
+ it::getTypeNode shouldBe classType("Foo") {
annotation("Lol")
@@ -84,9 +78,7 @@ class ASTConstructorCallTest : ParserTestSpec({
it::getQualifier shouldBe ambiguousName("a.g")
}
- it::getTypeNode shouldBe child {
- it::getTypeImage shouldBe "Foo"
- }
+ it::getTypeNode shouldBe classType("Foo")
it::getArguments shouldBe argList {
@@ -102,9 +94,7 @@ class ASTConstructorCallTest : ParserTestSpec({
it::getQualifier shouldBe variableAccess("a")
- it::getTypeNode shouldBe child {
- it::getTypeImage shouldBe "Foo"
- }
+ it::getTypeNode shouldBe classType("Foo")
it::getArguments shouldBe child {
@@ -153,8 +143,7 @@ class ASTConstructorCallTest : ParserTestSpec({
it::getExplicitTypeArguments shouldBe null
- it::getTypeNode shouldBe child {
- it::getTypeImage shouldBe "Foo"
+ it::getTypeNode shouldBe classType("Foo") {
annotation("Lol")
diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTFieldAccessTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTFieldAccessTest.kt
index d93223fc5f..3b80819213 100644
--- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTFieldAccessTest.kt
+++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTFieldAccessTest.kt
@@ -16,10 +16,7 @@ class ASTFieldAccessTest : ParserTestSpec({
fieldAccess("foo") {
it::getQualifier shouldBe child {
- it::getQualifier shouldBe child {
- it.typeArguments shouldBe null
- it.typeImage shouldBe "Type"
- }
+ it::getQualifier shouldBe classType("Type")
}
}
}
diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTSuperExpressionTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTSuperExpressionTest.kt
index d25bd99bfa..fcc563f20a 100644
--- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTSuperExpressionTest.kt
+++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTSuperExpressionTest.kt
@@ -42,9 +42,7 @@ class ASTSuperExpressionTest : ParserTestSpec({
methodCall("foo") {
it::getQualifier shouldBe child {
- it::getQualifier shouldBe child {
- it::getImage shouldBe "Type"
- }
+ it::getQualifier shouldBe classType("Type")
}
unspecifiedChild()
@@ -55,8 +53,7 @@ class ASTSuperExpressionTest : ParserTestSpec({
methodCall("foo") {
it::getQualifier shouldBe child {
- it::getQualifier shouldBe child {
- it::getImage shouldBe "ASTThisExpression"
+ it::getQualifier shouldBe qualClassType("net.sourceforge.pmd.lang.java.ast.ASTThisExpression") {
it::getTypeArguments shouldBe null
it::getQualifier shouldBe null
diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTThisExpressionTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTThisExpressionTest.kt
index d0558f2410..110aba2410 100644
--- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTThisExpressionTest.kt
+++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTThisExpressionTest.kt
@@ -20,16 +20,13 @@ class ASTThisExpressionTest : ParserTestSpec({
inContext(ExpressionParsingCtx) {
"Type.this" should parseAs {
thisExpr {
- child {
- it::getImage shouldBe "Type"
- }
+ classType("Type")
}
}
"net.sourceforge.pmd.lang.java.ast.ASTThisExpression.this" should parseAs {
thisExpr {
- child {
- it::getImage shouldBe "ASTThisExpression"
+ qualClassType("net.sourceforge.pmd.lang.java.ast.ASTThisExpression") {
it::getTypeArguments shouldBe null
it::getQualifier shouldBe null
diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTTypeTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTTypeTest.kt
index 94ea3d1f2f..44b767ab19 100644
--- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTTypeTest.kt
+++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTTypeTest.kt
@@ -16,8 +16,6 @@ class ASTTypeTest : ParserTestSpec({
"java.util.List" should parseAs {
classType("List") {
- it::getTypeImage shouldBe "java.util.List"
- it::getImage shouldBe "List"
it::getTypeArguments shouldBe null
it::getQualifier shouldBe null
@@ -31,25 +29,19 @@ class ASTTypeTest : ParserTestSpec({
classType("List") {
it::getQualifier shouldBe null
- it::getImage shouldBe "List"
it::getAmbiguousLhs shouldBe child {
it::getName shouldBe "java.util"
}
- it::getTypeArguments shouldBe child {
- child {
- it::getTypeImage shouldBe "F"
- }
+ it::getTypeArguments shouldBe typeArgList(1) {
+ classType("F")
}
}
}
"foo" should parseAs {
classType("foo") {
- it::getTypeImage shouldBe "foo"
- it::getImage shouldBe "foo"
-
it::getAmbiguousLhs shouldBe null
it::getQualifier shouldBe null
}
@@ -82,21 +74,15 @@ class ASTTypeTest : ParserTestSpec({
annotation("C")
annotation("K")
-
- it::getTypeImage shouldBe "java.util.Map"
}
}
"java.util.Map.@Foo Entry" should parseAs {
classType("Entry") {
- it::getTypeImage shouldBe "java.util.Map.Entry"
-
it::getQualifier shouldBe null
it::getAmbiguousLhs shouldBe child {
- it::getTypeImage shouldBe "java.util.Map"
- it::getImage shouldBe "java.util.Map"
it::getName shouldBe "java.util.Map"
}
@@ -120,14 +106,10 @@ class ASTTypeTest : ParserTestSpec({
"Foo.@A Bar.Brew" should parseAs {
classType("Brew") {
- it::getTypeImage shouldBe "Foo.Bar.Brew"
-
it::getQualifier shouldBe classType("Bar") {
- it::getTypeImage shouldBe "Foo.Bar"
it::getTypeArguments shouldBe null
it::getQualifier shouldBe classType("Foo") {
- it::getTypeImage shouldBe "Foo"
it::getTypeArguments shouldBe typeArgList {
classType("K")
diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/TestExtensions.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/TestExtensions.kt
index 6a0d754c43..c2cec258f0 100644
--- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/TestExtensions.kt
+++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/TestExtensions.kt
@@ -5,19 +5,16 @@ import io.kotlintest.Matcher
import io.kotlintest.Result
import io.kotlintest.matchers.collections.shouldBeEmpty
import io.kotlintest.matchers.types.shouldBeInstanceOf
+import io.kotlintest.shouldBe
import io.kotlintest.shouldNotBe
import net.sourceforge.pmd.internal.util.IteratorUtil
-import net.sourceforge.pmd.lang.ast.GenericToken
import net.sourceforge.pmd.lang.ast.Node
import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken
import net.sourceforge.pmd.lang.ast.test.NodeSpec
import net.sourceforge.pmd.lang.ast.test.ValuedNodeSpec
import net.sourceforge.pmd.lang.ast.test.shouldBe
-import net.sourceforge.pmd.lang.ast.test.shouldMatch
import net.sourceforge.pmd.lang.java.types.JPrimitiveType.PrimitiveTypeKind
import net.sourceforge.pmd.lang.java.types.JPrimitiveType.PrimitiveTypeKind.*
-import java.util.*
-import kotlin.reflect.KCallable
fun > C?.shouldContainAtMostOneOf(vararg expected: T) {
this shouldNotBe null
@@ -69,9 +66,9 @@ fun TreeNodeWrapper.annotation(spec: ValuedNodeSpec.annotation(name: String, spec: NodeSpec = EmptyAssertions) =
+fun TreeNodeWrapper.annotation(simpleName: String, spec: NodeSpec = EmptyAssertions) =
child(ignoreChildren = spec == EmptyAssertions) {
- it::getAnnotationName shouldBe name
+ it::getSimpleName shouldBe simpleName
spec()
}
@@ -370,9 +367,9 @@ fun TreeNodeWrapper.classType(simpleName: String, contents: NodeSpec.qualClassType(canoName: String, contents: NodeSpec = EmptyAssertions) =
child(ignoreChildren = contents == EmptyAssertions) {
- it::getCanonicalName shouldBe canoName
- it::getImage shouldBe canoName
- it::getSimpleName shouldBe canoName.substringAfterLast('.')
+ val simpleName = canoName.substringAfterLast('.')
+ it::getSimpleName shouldBe simpleName
+ it.text.toString() shouldBe canoName
contents()
}
diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/TypeDisambiguationTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/TypeDisambiguationTest.kt
index e21fa2b55c..4b00f6646d 100644
--- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/TypeDisambiguationTest.kt
+++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/TypeDisambiguationTest.kt
@@ -1,11 +1,13 @@
package net.sourceforge.pmd.lang.java.ast
import io.kotlintest.shouldBe
+import net.sourceforge.pmd.lang.ast.test.*
import net.sourceforge.pmd.lang.ast.test.shouldBe
-import net.sourceforge.pmd.lang.ast.test.shouldBeA
-import net.sourceforge.pmd.lang.ast.test.shouldMatchN
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol
import net.sourceforge.pmd.lang.java.symbols.table.internal.SemanticChecksLogger
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
class TypeDisambiguationTest : ParserTestSpec({
@@ -24,11 +26,12 @@ class TypeDisambiguationTest : ParserTestSpec({
val (f1) = acu.descendants(ASTFieldDeclaration::class.java).toList()
f1.typeNode.shouldMatchNode {
+ it::isFullyQualified shouldBe false
it::getSimpleName shouldBe "Inner"
- it::getImage shouldBe "Inner"
it::getReferencedSym shouldBe inner
it::getAmbiguousLhs shouldBe null
it::getQualifier shouldBe classType("Foo") {
+ it::isFullyQualified shouldBe false
it::getReferencedSym shouldBe foo
}
}
@@ -40,18 +43,19 @@ class TypeDisambiguationTest : ParserTestSpec({
inContext(TypeParsingCtx) {
"javasymbols.testdata.Statics" should parseAs {
- classType("Statics") {
- it::getImage shouldBe "javasymbols.testdata.Statics"
+ qualClassType("javasymbols.testdata.Statics") {
+ it::isFullyQualified shouldBe true
it::getQualifier shouldBe null
it::getAmbiguousLhs shouldBe null
}
}
"javasymbols.testdata.Statics.PublicStatic" should parseAs {
- classType("PublicStatic") {
+ qualClassType("javasymbols.testdata.Statics.PublicStatic") {
+ it::isFullyQualified shouldBe false
- it::getQualifier shouldBe classType("Statics") {
- it::getImage shouldBe "javasymbols.testdata.Statics"
+ it::getQualifier shouldBe qualClassType("javasymbols.testdata.Statics") {
+ it::isFullyQualified shouldBe true
it::getQualifier shouldBe null
it::getAmbiguousLhs shouldBe null
}
@@ -133,4 +137,101 @@ class TypeDisambiguationTest : ParserTestSpec({
refInScratch.referencedSym shouldBe aMem
}
}
+
+ parserTest("Malformed types") {
+ val logger = enableProcessing()
+
+ val acu = parser.parse("""
+ package p;
+ class Scratch {
+ static class K {}
+ static class Foo {}
+ class Inner {} // non-static
+
+ Scratch.Foo m0; // ok
+ Scratch.K m1; // error
+ Scratch.K m2; // ok
+ Scratch.Foo m3; // error
+ Scratch.Foo m4; // error
+ Scratch.Foo m5; // raw type, ok
+
+ Scratch s0; // ok
+ Scratch s1; // error
+ Scratch s2; // raw type, ok
+
+ // Scratch.Foo m6; // todo error: Foo is static
+ // Scratch.Foo m7; // todo error: Foo is static
+
+ // Scratch.Inner m; // ok, fully parameterized
+ // Scratch.Inner m; // todo error: Scratch must be parameterized
+ // Scratch.Inner m; // ok, raw type
+ }
+ """)
+
+ val (m0, m1, m2, m3, m4, m5, s0, s1, s2) =
+ acu.descendants(ASTFieldDeclaration::class.java).map { it.typeNode as ASTClassOrInterfaceType }.toList()
+
+ fun assertErrored(t: ASTClassOrInterfaceType, expected: Int, actual: Int) {
+ val errs = logger.errors[SemanticChecksLogger.MALFORMED_GENERIC_TYPE]?.filter { it.first == t } ?: emptyList()
+ assertEquals(errs.size, 1, "`${t.text}` should have produced a single error")
+ errs.single().second.toList() shouldBe listOf(expected, actual)
+ }
+
+ fun assertNoError(t: ASTClassOrInterfaceType) {
+ val err = logger.errors[SemanticChecksLogger.MALFORMED_GENERIC_TYPE]?.firstOrNull { it.first == t }
+ assertNull(err, "`${t.text}` should not have produced an error")
+ }
+
+ assertNoError(m0)
+ assertErrored(m1, expected = 0, actual = 1)
+ assertNoError(m2)
+ assertErrored(m3, expected = 2, actual = 1)
+ assertErrored(m4, expected = 2, actual = 3)
+ assertNoError(m5)
+
+ assertNoError(s0)
+ assertErrored(s1, expected = 1, actual = 2)
+ assertNoError(s2)
+ }
+
+ parserTest("Invalid annotations") {
+ val logger = enableProcessing()
+
+ val acu = parser.parse("""
+ package p;
+ class C {
+ @interface A { }
+ interface I { }
+
+
+ @T
+ @C
+ @I
+ @Unresolved
+ @A
+ int field;
+ }
+ """)
+
+ val (aT, aC, aI, aUnresolved, aOk) =
+ acu.descendants(ASTAnnotation::class.java).map { it.typeNode }.toList()
+
+ fun assertErrored(t: ASTClassOrInterfaceType) {
+ val errs = logger.errors[SemanticChecksLogger.EXPECTED_ANNOTATION_TYPE]?.filter { it.first == t } ?: emptyList()
+ assertEquals(errs.size, 1, "`${t.text}` should have produced a single error")
+ errs.single().second.toList() shouldBe emptyList()
+ }
+
+ fun assertNoError(t: ASTClassOrInterfaceType) {
+ val err = logger.errors[SemanticChecksLogger.MALFORMED_GENERIC_TYPE]?.firstOrNull { it.first == t }
+ assertNull(err, "`${t.text}` should not have produced an error")
+ }
+
+ assertNoError(aUnresolved)
+ assertNoError(aOk)
+
+ assertErrored(aT)
+ assertErrored(aC)
+ assertErrored(aI)
+ }
})
diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/symbols/table/internal/TypeParamScopingTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/symbols/table/internal/TypeParamScopingTest.kt
index c559ece244..7ac0cf4629 100644
--- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/symbols/table/internal/TypeParamScopingTest.kt
+++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/symbols/table/internal/TypeParamScopingTest.kt
@@ -4,6 +4,7 @@
package net.sourceforge.pmd.lang.java.symbols.table.internal
+import io.kotlintest.matchers.types.shouldBeSameInstanceAs
import io.kotlintest.shouldBe
import net.sourceforge.pmd.lang.ast.test.shouldBe
import net.sourceforge.pmd.lang.ast.test.shouldBeA
@@ -134,8 +135,8 @@ class TypeParamScopingTest : ParserTestSpec({
class Foo {
- @ /*Foo#*/ T // type params are not in scope in modifier list
- void foo(T pt, X px) {
+ @ /*Foo#*/ Y // type params of the method are not in scope in modifier list
+ void foo(T pt, X px) {
T vt;
X vx;
@@ -145,12 +146,14 @@ class TypeParamScopingTest : ParserTestSpec({
X vx2;
}
}
+
+ @interface Y { }
}
""")
// type parameters
- val (x, t, t2) = acu.descendants(ASTTypeParameter::class.java).toList()
+ val (x, t, t2, y2) = acu.descendants(ASTTypeParameter::class.java).toList()
// parameters
val (pt, px) = acu.descendants(ASTFormalParameter::class.java).map { it.typeNode }.toList()
@@ -159,7 +162,7 @@ class TypeParamScopingTest : ParserTestSpec({
val (vt, vx, vx2) = acu.descendants(ASTLocalVariableDeclaration::class.java).map { it.typeNode }.toList()
// classes
- val (_, localX) = acu.descendants(ASTClassOrInterfaceDeclaration::class.java).toList()
+ val (_, localX, annotY) = acu.descendants(ASTAnyTypeDeclaration::class.java).toList()
doTest("TParams of class are in scope inside method tparam declaration") {
@@ -189,7 +192,8 @@ class TypeParamScopingTest : ParserTestSpec({
val annot = acu.descendants(ASTAnnotation::class.java).first()!!
- annot.symbolTable.shouldResolveTypeTo("T", t.symbol) //not t2
+ annot.symbolTable.shouldResolveTypeTo("Y", annotY.symbol) // not the Y of the method
+ annot.symbol.shouldBeSameInstanceAs(annotY.symbol)
}
doTest("Local class shadows type param") {