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 java.util.Map;
import net.sourceforge.pmd.lang.ast.CharStream; import net.sourceforge.pmd.lang.ast.CharStream;
import net.sourceforge.pmd.lang.ast.TokenMgrError; import net.sourceforge.pmd.lang.ast.TokenMgrError;
import net.sourceforge.pmd.lang.ast.Node;
public class JavaParser { public class JavaParser {
private int jdkVersion = 0; private int jdkVersion = 0;
@ -1987,18 +1989,51 @@ void ReferenceType() #void:
| ( ClassOrInterfaceType() [ LOOKAHEAD(2) Dims() ] ) #ArrayType(>1) | ( ClassOrInterfaceType() [ LOOKAHEAD(2) Dims() ] ) #ArrayType(>1)
} }
void ClassOrInterfaceType(): void ClassOrInterfaceType() #void:
{ {
StringBuilder s = new StringBuilder(); StringBuilder s = new StringBuilder();
Token t; Token t;
} }
{ {
t=<IDENTIFIER> {s.append(t.image);} (
[ LOOKAHEAD(2) TypeArguments() ] (
( LOOKAHEAD(2) "." t=<IDENTIFIER> {s.append('.').append(t.image);} [ LOOKAHEAD(2) TypeArguments() ] )* t=<IDENTIFIER> { s.append(t.getImage()); }
{jjtThis.setImage(s.toString());} // 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(): void TypeArguments():
{} {}
{ {

View File

@ -5,15 +5,34 @@
package net.sourceforge.pmd.lang.java.ast; package net.sourceforge.pmd.lang.java.ast;
import java.util.Optional;
// @formatter:off
/** /**
* Represents a class or interface type, possibly parameterised with type arguments. * 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> * <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> * </pre>
*/ */
// @formatter:on
public class ASTClassOrInterfaceType extends AbstractJavaTypeNode implements ASTReferenceType { public class ASTClassOrInterfaceType extends AbstractJavaTypeNode implements ASTReferenceType {
public ASTClassOrInterfaceType(String identifier) { public ASTClassOrInterfaceType(String identifier) {
super(JavaParserTreeConstants.JJTCLASSORINTERFACETYPE); 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 @Override
public Object jjtAccept(JavaParserVisitor visitor, Object data) { public Object jjtAccept(JavaParserVisitor visitor, Object data) {
return visitor.visit(this, 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 * same compilation unit - either a class/interface or a enum type. You want
* to check this, if {@link #getType()} is null. * to check this, if {@link #getType()} is null.
* *
* @return <code>true</code> if this node referencing a type in the same * @return {@code true} if this node referencing a type in the same
* compilation unit, <code>false</code> otherwise. * compilation unit, {@code false} otherwise.
*/ */
public boolean isReferenceToClassSameCompilationUnit() { public boolean isReferenceToClassSameCompilationUnit() {
ASTCompilationUnit root = getFirstParentOfType(ASTCompilationUnit.class); 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.ast.Node;
import net.sourceforge.pmd.lang.symboltable.Scope; import net.sourceforge.pmd.lang.symboltable.Scope;
public abstract class AbstractJavaNode extends AbstractNode implements JavaNode { public abstract class AbstractJavaNode extends AbstractNode implements JavaNode {
protected JavaParser parser; protected JavaParser parser;
private Scope scope; private Scope scope;
private Comment comment; private Comment comment;
public AbstractJavaNode(int id) { public AbstractJavaNode(int id) {
super(id); super(id);
} }
public AbstractJavaNode(JavaParser parser, int id) { public AbstractJavaNode(JavaParser parser, int id) {
super(id); super(id);
this.parser = parser; this.parser = parser;
} }
@Override @Override
public void jjtOpen() { public void jjtOpen() {
if (beginLine == -1 && parser.token.next != null) { if (beginLine == -1 && parser.token.next != null) {
@ -31,6 +35,7 @@ public abstract class AbstractJavaNode extends AbstractNode implements JavaNode
} }
} }
@Override @Override
public void jjtClose() { public void jjtClose() {
if (beginLine == -1 && children.length == 0) { if (beginLine == -1 && children.length == 0) {
@ -43,6 +48,7 @@ public abstract class AbstractJavaNode extends AbstractNode implements JavaNode
endColumn = parser.token.endColumn; endColumn = parser.token.endColumn;
} }
/** /**
* Accept the visitor. * * Accept the visitor. *
*/ */
@ -88,20 +94,70 @@ public abstract class AbstractJavaNode extends AbstractNode implements JavaNode
return scope; return scope;
} }
@Override @Override
public void setScope(Scope scope) { public void setScope(Scope scope) {
this.scope = scope; this.scope = scope;
} }
public void comment(Comment theComment) { public void comment(Comment theComment) {
comment = theComment; comment = theComment;
} }
public Comment comment() { public Comment comment() {
return 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 @Override
public final String getXPathNodeName() { public final String getXPathNodeName() {