[java] Rework BlockStatement to allow local interfaces, enums, ...
This commit is contained in:
@ -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(); }
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
|
@ -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() {
|
||||
|
@ -63,6 +63,7 @@ public final class ASTRecordDeclaration extends AbstractAnyTypeDeclaration {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLocal() {
|
||||
return getParent() instanceof ASTBlockStatement;
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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 {}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user