Parse ClassOrInterfaceType recursively

This commit is contained in:
Clément Fournier
2018-11-06 04:22:31 +01:00
committed by Andreas Dangel
parent 8a588f565f
commit 18581b82ae
3 changed files with 129 additions and 8 deletions

View File

@ -214,6 +214,8 @@ import java.util.List;
import java.util.Map;
import net.sourceforge.pmd.lang.ast.CharStream;
import net.sourceforge.pmd.lang.ast.TokenMgrError;
import net.sourceforge.pmd.lang.ast.Node;
public class JavaParser {
private int jdkVersion = 0;
@ -1987,18 +1989,51 @@ void ReferenceType() #void:
| ( ClassOrInterfaceType() [ LOOKAHEAD(2) Dims() ] ) #ArrayType(>1)
}
void ClassOrInterfaceType():
void ClassOrInterfaceType() #void:
{
StringBuilder s = new StringBuilder();
Token t;
}
{
t=<IDENTIFIER> {s.append(t.image);}
[ LOOKAHEAD(2) TypeArguments() ]
( LOOKAHEAD(2) "." t=<IDENTIFIER> {s.append('.').append(t.image);} [ LOOKAHEAD(2) TypeArguments() ] )*
{jjtThis.setImage(s.toString());}
(
(
t=<IDENTIFIER> { s.append(t.getImage()); }
// We gobble up all identifiers until we find
// either type arguments or annotations, because
// it may be a FQCN
( LOOKAHEAD("." <IDENTIFIER>)
"." t=<IDENTIFIER> { s.append('.').append(t.getImage()); }
)*
)
[ LOOKAHEAD( "<" ) TypeArguments() ]
) #ClassOrInterfaceType
{
// At this point the first ClassOrInterfaceType is on top of the stack
jjtree.peekNode().setImage(s.toString());
}
( LOOKAHEAD(2)
(
"."
(TypeAnnotation())*
t=<IDENTIFIER> { jjtThis.setImage(t.getImage()); }
[ LOOKAHEAD( "<" ) TypeArguments() ]
) #ClassOrInterfaceType {
// At this point the newer ClassOrInterfaceType has been pushed on the stack
// We retrieve it
AbstractJavaNode lastSegment = (AbstractJavaNode) jjtree.popNode();
// All its children (annotations and type arguments) have already been popped,
// so the node remaining on top of the stack is the previous segment
Node previousSegment = jjtree.popNode();
lastSegment.insertChild((JavaNode) previousSegment, 0, true);
jjtree.pushNode(lastSegment);
}
)*
}
void TypeArguments():
{}
{

View File

@ -5,15 +5,34 @@
package net.sourceforge.pmd.lang.java.ast;
import java.util.Optional;
// @formatter:off
/**
* Represents a class or interface type, possibly parameterised with type arguments.
* This node comes in two productions (see below):
*
* <p>The first is a left-recursive variant, allowing to parse references to type members
* unambiguously. The resulting node's {@linkplain #getLeftHandSide() left-hand-side type}
* addresses the type parent of the type. The position of type arguments and annotations are
* preserved.
*
* <p>Parsing types left-recursively has a caveat though: fully-qualified type names. The
* parser can't disambiguate between a reference to a type member (e.g. {@code Map.Entry}, where
* both segments refer to a type, and which would ideally be parsed left-recursively), and a
* qualified type name (e.g. {@code java.util.List}, where the full sequence refers to a unique
* type, but individual segments don't).
*
* <p>We could remove that with a later AST visit, like type resolution though!
*
* <pre>
*
* ClassOrInterfaceType ::= &lt;IDENTIFIER&gt; {@linkplain ASTTypeArguments TypeArguments}? ( "." &lt;IDENTIFIER&gt; {@linkplain ASTTypeArguments TypeArguments}? )*
* ClassOrInterfaceType ::= ClassOrInterfaceType ( "." {@linkplain ASTAnnotation Annotation}* &lt;IDENTIFIER&gt; {@linkplain ASTTypeArguments TypeArguments}? )+
* | &lt;IDENTIFIER&gt; ( "." &lt;IDENTIFIER&gt; ) * {@linkplain ASTTypeArguments TypeArguments}?
*
* </pre>
*/
// @formatter:on
public class ASTClassOrInterfaceType extends AbstractJavaTypeNode implements ASTReferenceType {
public ASTClassOrInterfaceType(String identifier) {
super(JavaParserTreeConstants.JJTCLASSORINTERFACETYPE);
@ -31,6 +50,17 @@ public class ASTClassOrInterfaceType extends AbstractJavaTypeNode implements AST
}
/**
* Gets the left-hand side type of this type. This is a type we know for sure
* that this type is a member of.
*
* @return A type, or null if this is a base type
*/
public Optional<ASTClassOrInterfaceType> getLeftHandSide() {
return Optional.ofNullable(getFirstChildOfType(ASTClassOrInterfaceType.class));
}
@Override
public Object jjtAccept(JavaParserVisitor visitor, Object data) {
return visitor.visit(this, data);
@ -48,8 +78,8 @@ public class ASTClassOrInterfaceType extends AbstractJavaTypeNode implements AST
* same compilation unit - either a class/interface or a enum type. You want
* to check this, if {@link #getType()} is null.
*
* @return <code>true</code> if this node referencing a type in the same
* compilation unit, <code>false</code> otherwise.
* @return {@code true} if this node referencing a type in the same
* compilation unit, {@code false} otherwise.
*/
public boolean isReferenceToClassSameCompilationUnit() {
ASTCompilationUnit root = getFirstParentOfType(ASTCompilationUnit.class);

View File

@ -8,21 +8,25 @@ import net.sourceforge.pmd.lang.ast.AbstractNode;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.symboltable.Scope;
public abstract class AbstractJavaNode extends AbstractNode implements JavaNode {
protected JavaParser parser;
private Scope scope;
private Comment comment;
public AbstractJavaNode(int id) {
super(id);
}
public AbstractJavaNode(JavaParser parser, int id) {
super(id);
this.parser = parser;
}
@Override
public void jjtOpen() {
if (beginLine == -1 && parser.token.next != null) {
@ -31,6 +35,7 @@ public abstract class AbstractJavaNode extends AbstractNode implements JavaNode
}
}
@Override
public void jjtClose() {
if (beginLine == -1 && children.length == 0) {
@ -43,6 +48,7 @@ public abstract class AbstractJavaNode extends AbstractNode implements JavaNode
endColumn = parser.token.endColumn;
}
/**
* Accept the visitor. *
*/
@ -88,20 +94,70 @@ public abstract class AbstractJavaNode extends AbstractNode implements JavaNode
return scope;
}
@Override
public void setScope(Scope scope) {
this.scope = scope;
}
public void comment(Comment theComment) {
comment = theComment;
}
public Comment comment() {
return comment;
}
// insert a child at a given index, shifting other children if need be
// The implementation of jjtAddChild in AbstractNode overwrites nodes
// -> probably unexpected and to be changed
// parser only
void insertChild(JavaNode child, int index, boolean expandTextSpan) {
// Allow to insert a child at random index without overwriting
// If the child is null, it is replaced. If it is not null, children are shifted
if (children != null && index < children.length && children[index] != null) {
Node[] newChildren = new Node[children.length + 1];
// toShift nodes are to the right of the insertion index
int toShift = children.length - index;
// copy the nodes before
System.arraycopy(children, 0, newChildren, 0, index);
// copy the nodes after
System.arraycopy(children, index, newChildren, index + 1, toShift);
children = newChildren;
}
super.jjtAddChild(child, index);
child.jjtSetParent(this);
// The text coordinates of this node will be enlarged with those of the child
if (expandTextSpan) {
AbstractJavaNode childImpl = (AbstractJavaNode) child;
if (this.beginLine > childImpl.beginLine) {
this.beginLine = childImpl.beginLine;
this.beginColumn = childImpl.beginColumn;
} else if (this.beginLine == childImpl.beginLine
&& this.beginColumn > childImpl.beginColumn) {
this.beginColumn = childImpl.beginColumn;
}
if (this.endLine < childImpl.endLine) {
this.endLine = childImpl.endLine;
this.endColumn = childImpl.endColumn;
} else if (this.endLine == childImpl.endLine
&& this.endColumn < childImpl.endColumn) {
this.endColumn = childImpl.endColumn;
}
// TODO tokens
}
}
@Override
public final String getXPathNodeName() {