[java] Rework BlockStatement to allow local interfaces, enums, ...

This commit is contained in:
Andreas Dangel
2020-08-16 21:37:39 +02:00
parent fceb474df0
commit 67b37de334
8 changed files with 140 additions and 46 deletions

View File

@ -239,9 +239,11 @@ options {
PARSER_BEGIN(JavaParser)
package net.sourceforge.pmd.lang.java.ast;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import net.sourceforge.pmd.lang.ast.CharStream;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.ast.TokenMgrError;
public class JavaParser {
@ -484,28 +486,22 @@ public class JavaParser {
}
}
private void checkForLocalInterfaceOrEnumType() {
if (!isRecordTypeSupported()) {
throwParseException("Local interfaces and enums are only supported with Java 14 Preview and Java 15 Preview");
}
}
private boolean isRecordTypeSupported() {
return (jdkVersion == 14 || jdkVersion == 15) && preview;
}
private boolean isRecordDeclarationAhead() {
int amount = 1;
while (amount < 10) {
Token next = getToken(amount);
if (next.kind == IDENTIFIER && "record".equals(next.image)) {
return true;
}
// stop looking ahead at "=" or ";" or "{"
if (next.kind == ASSIGN || next.kind == LBRACE || next.kind == SEMICOLON) {
return false;
}
amount++;
}
return false;
private boolean isSealedClassSupported() {
return jdkVersion == 15 && preview;
}
private void checkForSealedClassUsage() {
if (jdkVersion != 15 || !preview) {
if (!isSealedClassSupported()) {
throwParseException("Sealed Classes are only supported with Java 15 Preview");
}
}
@ -573,6 +569,29 @@ public class JavaParser {
return false;
}
private boolean classModifierLookahead() {
Token next = getToken(1);
return next.kind == AT
|| next.kind == PUBLIC
|| next.kind == PROTECTED
|| next.kind == PRIVATE
|| next.kind == ABSTRACT
|| next.kind == STATIC
|| next.kind == FINAL
|| next.kind == STRICTFP
|| isSealedClassSupported() && isKeyword("sealed")
|| isSealedClassSupported() && isNonSealedModifier();
}
private boolean localTypeDeclLookahead() {
Token next = getToken(1);
return next.kind == CLASS
|| isRecordTypeSupported() && next.kind == INTERFACE
|| isRecordTypeSupported() && next.kind == AT && isToken(2, INTERFACE)
|| isRecordTypeSupported() && next.kind == IDENTIFIER && next.image.equals("enum")
|| isRecordTypeSupported() && next.kind == IDENTIFIER && next.image.equals("record");
}
/**
* True if we're in a switch block, one precondition for parsing a yield
* statement.
@ -1963,42 +1982,63 @@ void Block() :
}
void BlockStatement():
{}
{int mods = 0;}
{
LOOKAHEAD( { isNextTokenAnAssert() } ) AssertStatement()
| LOOKAHEAD( { isYieldStart() } ) YieldStatement()
|
LOOKAHEAD(2147483647, ( "final" | Annotation() )* Type() <IDENTIFIER>, {isRecordTypeSupported() && !isRecordDeclarationAhead() || !isRecordTypeSupported()})
LocalVariableDeclaration() ";"
|
LOOKAHEAD(1, {isRecordTypeSupported() && !isRecordDeclarationAhead() || !isRecordTypeSupported()})
Statement()
|
// we don't need to lookahead further here
// the ambiguity between start of local class and local variable decl
// and start of local record and local variable decl or statement
// is already handled in the lookahead guarding LocalVariableDeclaration
// and Statement above.
LocalClassOrRecordDecl()
}
void LocalClassOrRecordDecl() #void:
{int mods = 0;}
{
// this preserves the modifiers of the local class or record.
// it allows for modifiers that are forbidden for local classes and records,
// but anyway we are *not* checking modifiers for incompatibilities
// anywhere else in this grammar (and indeed the production Modifiers
// accepts any modifier explicitly for the purpose of forgiving modifier errors,
// and reporting them later if needed --see its documentation).
// In particular, it unfortunately allows local class declarations to start
// with a "default" modifier, which introduces an ambiguity with default
// switch labels. This is guarded by a custom lookahead around SwitchLabel
| LOOKAHEAD( "@" | "final" )
// this eagerly parses all modifiers and annotations. After that, either a local type declaration
// or a local variable declaration follows.
// This allows more modifiers for local variables than actually allowed
// and the annotations for local variables need to be moved in the AST down again.
mods=Modifiers()
(
LOOKAHEAD({isKeyword("record")}) RecordDeclaration(mods)
| ClassOrInterfaceDeclaration(mods)
LOOKAHEAD({localTypeDeclLookahead()}) LocalTypeDecl(mods)
|
{
List<Node> annotationsAndChildren = new ArrayList<Node>();
while (jjtree.peekNode() instanceof ASTAnnotation) {
annotationsAndChildren.add(jjtree.popNode());
}
}
LocalVariableDeclaration()
{
ASTLocalVariableDeclaration localVarDecl = (ASTLocalVariableDeclaration) jjtree.peekNode();
if ((mods & AccessNode.FINAL) == AccessNode.FINAL) {
localVarDecl.setFinal(true);
}
if (!annotationsAndChildren.isEmpty()) {
Collections.reverse(annotationsAndChildren);
for (int i = 0; i < localVarDecl.getNumChildren(); i++) {
annotationsAndChildren.add(localVarDecl.getChild(i));
}
for (int i = 0; i < annotationsAndChildren.size(); i++) {
Node child = annotationsAndChildren.get(i);
child.jjtSetParent(localVarDecl);
localVarDecl.jjtAddChild(child, i);
}
}
}
";"
)
| LOOKAHEAD({classModifierLookahead() || localTypeDeclLookahead()})
mods=Modifiers()
LocalTypeDecl(mods)
| LOOKAHEAD(Type() <IDENTIFIER>)
LocalVariableDeclaration() ";"
|
Statement()
}
void LocalTypeDecl(int mods) #void:
{}
{
(
LOOKAHEAD(<CLASS>) ClassOrInterfaceDeclaration(mods)
| LOOKAHEAD(<INTERFACE>) ClassOrInterfaceDeclaration(mods) { checkForLocalInterfaceOrEnumType(); }
| LOOKAHEAD({isKeyword("record")}) RecordDeclaration(mods) { checkForLocalInterfaceOrEnumType(); }
| LOOKAHEAD({isKeyword("enum")}) EnumDeclaration(mods) { checkForLocalInterfaceOrEnumType(); }
| AnnotationTypeDeclaration(mods) { checkForLocalInterfaceOrEnumType(); }
)
}

View File

@ -34,6 +34,10 @@ public class ASTAnnotationTypeDeclaration extends AbstractAnyTypeDeclaration {
return TypeKind.ANNOTATION;
}
@Override
public boolean isLocal() {
return getParent() instanceof ASTBlockStatement;
}
@Override
public List<ASTAnyTypeBodyDeclaration> getDeclarations() {

View File

@ -78,6 +78,10 @@ public interface ASTAnyTypeDeclaration extends TypeNode, JavaQualifiableNode, Ac
*/
boolean isNested();
/**
* Returns true if this type is declared locally, e.g. in the context of a method block.
*/
boolean isLocal();
/**
* The kind of type this node declares.

View File

@ -65,6 +65,7 @@ public class ASTClassOrInterfaceDeclaration extends AbstractAnyTypeDeclaration {
* Returns true if the class is declared inside a block other
* than the body of another class, or the top level.
*/
@Override
public boolean isLocal() {
if (!isLocalComputed) {
Node current = getParent();

View File

@ -34,6 +34,10 @@ public class ASTEnumDeclaration extends AbstractAnyTypeDeclaration {
return TypeKind.ENUM;
}
@Override
public boolean isLocal() {
return getParent() instanceof ASTBlockStatement;
}
@Override
public List<ASTAnyTypeBodyDeclaration> getDeclarations() {

View File

@ -63,6 +63,7 @@ public final class ASTRecordDeclaration extends AbstractAnyTypeDeclaration {
return true;
}
@Override
public boolean isLocal() {
return getParent() instanceof ASTBlockStatement;
}

View File

@ -207,4 +207,25 @@ public class Java15PreviewTest {
Assert.assertEquals("TimesExpr", permittedSubclasses.get(2).getImage());
Assert.assertEquals("NegExpr", permittedSubclasses.get(3).getImage());
}
@Test
public void localInterfaceAndEnums() {
ASTCompilationUnit compilationUnit = java15p.parseResource("LocalInterfacesAndEnums.java");
List<ASTAnyTypeDeclaration> types = compilationUnit.findDescendantsOfType(ASTAnyTypeDeclaration.class);
Assert.assertEquals(5, types.size());
Assert.assertTrue(types.get(0) instanceof ASTClassOrInterfaceDeclaration);
Assert.assertFalse(types.get(0).isLocal());
Assert.assertTrue(types.get(1) instanceof ASTClassOrInterfaceDeclaration);
Assert.assertTrue(types.get(2) instanceof ASTClassOrInterfaceDeclaration);
Assert.assertTrue(types.get(3) instanceof ASTEnumDeclaration);
Assert.assertTrue(types.get(4) instanceof ASTAnnotationTypeDeclaration);
for (int i = 1; i < 5; i++) {
Assert.assertTrue(types.get(i).isLocal());
}
}
@Test(expected = ParseException.class)
public void localInterfacesAndEnumsBeforeJava15PreviewShouldFail() {
java15.parseResource("LocalInterfacesAndEnums.java");
}
}

View File

@ -0,0 +1,19 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
public class LocalInterfacesAndEnums {
{
class MyLocalClass {}
// static local classes are not allowed (neither Java15 nor Java15 Preview)
//static class MyLocalStaticClass {}
interface MyLocalInterface {}
enum MyLocalEnum { A }
@interface MyLocalAnnotation {}
}
}