Merge branch 'grammar-annot-disambig' into typeres-jtypes
This commit is contained in:
@ -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");
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
*
|
||||
* <pre class="grammar">
|
||||
*
|
||||
* Annotation ::= "@" Name {@link ASTAnnotationMemberList AnnotationMemberList}?
|
||||
* Annotation ::= "@" {@link ASTClassOrInterfaceType ClassName} {@link ASTAnnotationMemberList AnnotationMemberList}?
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public final class ASTAnnotation extends AbstractJavaTypeNode implements TypeNode, ASTMemberValue, Iterable<ASTMemberValuePair> {
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
|
@ -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<ASTClassOrInterfaceType> getAllExceptionTypes() {
|
||||
ASTType typeNode = getTypeNode();
|
||||
if (typeNode instanceof ASTUnionType) {
|
||||
return typeNode.children(ASTClassOrInterfaceType.class);
|
||||
} else {
|
||||
return NodeStream.of((ASTClassOrInterfaceType) typeNode);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -99,6 +99,10 @@ public abstract class ASTList<N extends JavaNode> 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 <T>}
|
||||
* as a child.
|
||||
|
@ -32,23 +32,4 @@ public final class ASTThrowStatement extends AbstractStatement implements ASTSwi
|
||||
return (ASTExpression) getFirstChild();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the image of the first ASTClassOrInterfaceType child or
|
||||
* <code>null</code> 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 <code>null</code> is when the throws is like
|
||||
* throw this.e or throw this.
|
||||
*
|
||||
* This is too specific
|
||||
*
|
||||
* <p>TODO - use symbol table (?)</p>
|
||||
*
|
||||
* @return the image of the first ASTClassOrInterfaceType node found or
|
||||
* <code>null</code>
|
||||
*/
|
||||
public String getFirstClassOrInterfaceTypeImage() {
|
||||
final ASTClassOrInterfaceType t = getFirstDescendantOfType(ASTClassOrInterfaceType.class);
|
||||
return t == null ? null : t.getImage();
|
||||
}
|
||||
}
|
||||
|
@ -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<ASTCatchClause> getCatchClauses() {
|
||||
return findChildrenOfType(ASTCatchClause.class);
|
||||
public NodeStream<ASTCatchClause> getCatchClauses() {
|
||||
return children(ASTCatchClause.class);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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 <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.5.2">JLS§6.5.2</a>.
|
||||
@ -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<JavaccToken> 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<JavaccToken> 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<JavaccToken> remaining) {
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ public class IdenticalCatchBranchesRule extends AbstractJavaRule {
|
||||
@Override
|
||||
public Object visit(ASTTryStatement node, Object data) {
|
||||
|
||||
List<ASTCatchClause> catchStatements = node.getCatchClauses();
|
||||
List<ASTCatchClause> catchStatements = node.getCatchClauses().toList();
|
||||
Set<List<ASTCatchClause>> equivClasses = equivalenceClasses(catchStatements);
|
||||
|
||||
for (List<ASTCatchClause> identicalStmts : equivClasses) {
|
||||
|
@ -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().<JavaNode>map(NodeStream.asInstanceOf(ASTTryStatement.class, ASTCatchClause.class)).first();
|
||||
NodeStream<ASTTryStatement> 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<ASTCatchClause> 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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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<ASTImportDeclaration> 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) {
|
||||
|
@ -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}";
|
||||
}
|
@ -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
|
||||
|
@ -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<Node> 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 {
|
||||
|
@ -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) {
|
||||
|
@ -31,18 +31,20 @@ class ASTAnnotationTest : ParserTestSpec({
|
||||
|
||||
"@F" should parseAs {
|
||||
child<ASTAnnotation> {
|
||||
it::getAnnotationName shouldBe "F"
|
||||
it::getSimpleName shouldBe "F"
|
||||
|
||||
it::getTypeNode shouldBe classType("F")
|
||||
|
||||
it::getMemberList shouldBe null
|
||||
}
|
||||
}
|
||||
|
||||
"@java.lang.Override" should parseAs {
|
||||
child<ASTAnnotation> {
|
||||
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<ASTAnnotation> {
|
||||
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<ASTAnnotation> {
|
||||
it::getAnnotationName shouldBe "org.F"
|
||||
it::getSimpleName shouldBe "F"
|
||||
|
||||
it::getTypeNode shouldBe qualClassType("org.F")
|
||||
|
||||
it::getMemberList shouldBe child {
|
||||
shorthandMemberValue {
|
||||
child<ASTMemberValueArrayInitializer> {
|
||||
@ -87,9 +91,9 @@ class ASTAnnotationTest : ParserTestSpec({
|
||||
|
||||
"@org.F({@Aha, @Oh})" should parseAs {
|
||||
child<ASTAnnotation> {
|
||||
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<ASTAnnotation> {
|
||||
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<ASTAnnotation> {
|
||||
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<ASTAnnotation> {
|
||||
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<ASTAnnotation> {
|
||||
|
||||
it::getTypeNode shouldBe classType("TestAnnotation")
|
||||
|
||||
it::getMemberList shouldBe child {
|
||||
|
||||
shorthandMemberValue {
|
||||
@ -174,6 +180,8 @@ class ASTAnnotationTest : ParserTestSpec({
|
||||
child<ASTMemberValueArrayInitializer> {
|
||||
annotation {
|
||||
|
||||
it::getTypeNode shouldBe classType("SuppressWarnings")
|
||||
|
||||
it::getMemberList shouldBe child {
|
||||
shorthandMemberValue {
|
||||
child<ASTMemberValueArrayInitializer> {}
|
||||
@ -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<ASTMemberValueArrayInitializer> {
|
||||
|
@ -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")
|
||||
|
||||
|
@ -16,10 +16,7 @@ class ASTFieldAccessTest : ParserTestSpec({
|
||||
fieldAccess("foo") {
|
||||
|
||||
it::getQualifier shouldBe child<ASTThisExpression> {
|
||||
it::getQualifier shouldBe child {
|
||||
it.typeArguments shouldBe null
|
||||
it.typeImage shouldBe "Type"
|
||||
}
|
||||
it::getQualifier shouldBe classType("Type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,9 +42,7 @@ class ASTSuperExpressionTest : ParserTestSpec({
|
||||
methodCall("foo") {
|
||||
|
||||
it::getQualifier shouldBe child<ASTSuperExpression> {
|
||||
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<ASTSuperExpression> {
|
||||
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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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<ASTClassOrInterfaceType> {
|
||||
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<K, V>" 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<K>.@A Bar.Brew<V>" 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")
|
||||
|
@ -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 <T, C : Collection<T>> C?.shouldContainAtMostOneOf(vararg expected: T) {
|
||||
this shouldNotBe null
|
||||
@ -69,9 +66,9 @@ fun TreeNodeWrapper<Node, *>.annotation(spec: ValuedNodeSpec<ASTAnnotation, Unit
|
||||
child(ignoreChildren = spec == EmptyAssertions, nodeSpec = spec)
|
||||
|
||||
|
||||
fun TreeNodeWrapper<Node, *>.annotation(name: String, spec: NodeSpec<ASTAnnotation> = EmptyAssertions) =
|
||||
fun TreeNodeWrapper<Node, *>.annotation(simpleName: String, spec: NodeSpec<ASTAnnotation> = EmptyAssertions) =
|
||||
child<ASTAnnotation>(ignoreChildren = spec == EmptyAssertions) {
|
||||
it::getAnnotationName shouldBe name
|
||||
it::getSimpleName shouldBe simpleName
|
||||
spec()
|
||||
}
|
||||
|
||||
@ -370,9 +367,9 @@ fun TreeNodeWrapper<Node, *>.classType(simpleName: String, contents: NodeSpec<AS
|
||||
|
||||
fun TreeNodeWrapper<Node, *>.qualClassType(canoName: String, contents: NodeSpec<ASTClassOrInterfaceType> = EmptyAssertions) =
|
||||
child<ASTClassOrInterfaceType>(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()
|
||||
}
|
||||
|
||||
|
@ -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<ASTClassOrInterfaceType> {
|
||||
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<X> {
|
||||
static class K {}
|
||||
static class Foo<Y, X> {}
|
||||
class Inner<Y> {} // non-static
|
||||
|
||||
Scratch.Foo<K, K> m0; // ok
|
||||
Scratch.K<K> m1; // error
|
||||
Scratch.K m2; // ok
|
||||
Scratch.Foo<K> m3; // error
|
||||
Scratch.Foo<K, K, K> m4; // error
|
||||
Scratch.Foo m5; // raw type, ok
|
||||
|
||||
Scratch<K> s0; // ok
|
||||
Scratch<K, K> s1; // error
|
||||
Scratch s2; // raw type, ok
|
||||
|
||||
// Scratch<K>.Foo m6; // todo error: Foo is static
|
||||
// Scratch<K>.Foo<K, K> m7; // todo error: Foo is static
|
||||
|
||||
// Scratch<K>.Inner<K, K> m; // ok, fully parameterized
|
||||
// Scratch.Inner<K, K> 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<T> {
|
||||
@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)
|
||||
}
|
||||
})
|
||||
|
@ -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<X, T> {
|
||||
|
||||
@ /*Foo#*/ T // type params are not in scope in modifier list
|
||||
<T extends /*Foo#*/ X> void foo(T pt, X px) {
|
||||
@ /*Foo#*/ Y // type params of the method are not in scope in modifier list
|
||||
<T extends /*Foo#*/ X, Y> 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") {
|
||||
|
Reference in New Issue
Block a user