Annotation disambiguation
This commit is contained in:
@ -2392,7 +2392,7 @@ void RSIGNEDSHIFT() #void:
|
||||
void Annotation():
|
||||
{}
|
||||
{
|
||||
"@" jjtThis.name=VoidName() [ AnnotationMemberList() ]
|
||||
"@" ClassName() [ AnnotationMemberList() ]
|
||||
}
|
||||
|
||||
void AnnotationMemberList():
|
||||
@ -2556,6 +2556,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
|
||||
|
@ -9,45 +9,46 @@ 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
|
||||
return (JClassSymbol) getTypeNode().getReferencedSym();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the simple name of the annotation.
|
||||
*/
|
||||
public String getSimpleName() {
|
||||
return StringUtil.substringAfterLast(getImage(), '.');
|
||||
return getTypeNode().getSimpleName();
|
||||
}
|
||||
|
||||
|
||||
|
@ -9,6 +9,7 @@ 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.ASTConstructorDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
|
||||
@ -31,7 +32,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;
|
||||
}
|
||||
@ -83,6 +84,11 @@ 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(ASTMethodDeclaration node, Void data) {
|
||||
return new Attribute(node, "Name", node.getName());
|
||||
|
@ -17,11 +17,10 @@ import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTStringLiteral;
|
||||
import net.sourceforge.pmd.lang.java.ast.Annotatable;
|
||||
import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper;
|
||||
|
||||
/**
|
||||
* Helper methods to suppress violations based on annotations.
|
||||
@ -107,9 +106,8 @@ final class AnnotationSuppressionUtil {
|
||||
|
||||
// @formatter:on
|
||||
private static boolean annotationSuppresses(ASTAnnotation annotation, Rule rule) {
|
||||
// if (SuppressWarnings.class.equals(getType())) { // typeres is not always on
|
||||
if (TypeHelper.isA(annotation, SuppressWarnings.class)) {
|
||||
for (ASTLiteral element : annotation.findDescendantsOfType(ASTLiteral.class)) {
|
||||
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
|
||||
|
@ -8,18 +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 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;
|
||||
import net.sourceforge.pmd.lang.java.typeresolution.internal.NullableClassLoader.ClassLoaderWrapper;
|
||||
@ -91,21 +93,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) {
|
||||
|
@ -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", disambiguated = false)
|
||||
|
||||
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", disambiguated = false)
|
||||
|
||||
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", disambiguated = false)
|
||||
|
||||
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", disambiguated = false)
|
||||
|
||||
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", disambiguated = false)
|
||||
|
||||
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> {
|
||||
|
@ -7,17 +7,13 @@ import io.kotlintest.matchers.collections.shouldBeEmpty
|
||||
import io.kotlintest.matchers.types.shouldBeInstanceOf
|
||||
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.ast.ASTPrimitiveType.PrimitiveType
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTPrimitiveType.PrimitiveType.*
|
||||
import java.util.*
|
||||
import kotlin.reflect.KCallable
|
||||
|
||||
fun <T, C : Collection<T>> C?.shouldContainAtMostOneOf(vararg expected: T) {
|
||||
this shouldNotBe null
|
||||
@ -69,9 +65,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()
|
||||
}
|
||||
|
||||
@ -375,10 +371,16 @@ fun TreeNodeWrapper<Node, *>.classType(simpleName: String, contents: NodeSpec<AS
|
||||
contents()
|
||||
}
|
||||
|
||||
fun TreeNodeWrapper<Node, *>.qualClassType(canoName: String, contents: NodeSpec<ASTClassOrInterfaceType> = EmptyAssertions) =
|
||||
fun TreeNodeWrapper<Node, *>.qualClassType(canoName: String, disambiguated: Boolean = true, contents: NodeSpec<ASTClassOrInterfaceType> = EmptyAssertions) =
|
||||
child<ASTClassOrInterfaceType>(ignoreChildren = contents == EmptyAssertions) {
|
||||
it::getImage shouldBe canoName
|
||||
it::getSimpleName shouldBe canoName.substringAfterLast('.')
|
||||
val simpleName = canoName.substringAfterLast('.')
|
||||
if (disambiguated) {
|
||||
it::getImage shouldBe canoName
|
||||
it::getSimpleName shouldBe simpleName
|
||||
} else {
|
||||
it::getImage shouldBe simpleName
|
||||
it::getTypeImage shouldBe canoName
|
||||
}
|
||||
contents()
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
@ -190,6 +191,7 @@ class TypeParamScopingTest : ParserTestSpec({
|
||||
val annot = acu.descendants(ASTAnnotation::class.java).first()!!
|
||||
|
||||
annot.symbolTable.shouldResolveTypeTo("T", t.symbol) //not t2
|
||||
annot.symbol.shouldBeSameInstanceAs(t.symbol)
|
||||
}
|
||||
|
||||
doTest("Local class shadows type param") {
|
||||
|
Reference in New Issue
Block a user