From 1020bf73db5cb0ba7c45987af74781a2834db2c9 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Fri, 22 Sep 2017 21:35:24 +0200 Subject: [PATCH] Enhance grammar to parse Java 9 module-info.java --- pmd-java/etc/grammar/Java.jjt | 57 ++++++++++++- .../lang/java/ast/ASTModuleDeclaration.java | 33 ++++++++ .../pmd/lang/java/ast/ASTModuleDirective.java | 50 ++++++++++++ .../pmd/lang/java/ast/ASTModuleName.java | 27 +++++++ .../pmd/lang/java/ast/DumpFacade.java | 6 ++ .../java/ast/JavaParserDecoratedVisitor.java | 19 +++++ .../java/ast/JavaParserVisitorAdapter.java | 12 +++ .../java/ast/JavaParserVisitorDecorator.java | 18 +++++ .../pmd/lang/java/rule/AbstractJavaRule.java | 12 +++ .../java/ast/ASTModuleDeclarationTest.java | 79 +++++++++++++++++++ .../pmd/lang/java/ast/JDKVersionTest.java | 10 +++ .../ast/jdkversiontests/jdk9_module_info.java | 15 ++++ 12 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTModuleDeclaration.java create mode 100644 pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTModuleDirective.java create mode 100644 pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTModuleName.java create mode 100644 pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTModuleDeclarationTest.java create mode 100644 pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/jdk9_module_info.java diff --git a/pmd-java/etc/grammar/Java.jjt b/pmd-java/etc/grammar/Java.jjt index f4c747ba3b..c06cf10bc9 100644 --- a/pmd-java/etc/grammar/Java.jjt +++ b/pmd-java/etc/grammar/Java.jjt @@ -3,6 +3,7 @@ * Private interface methods are only allowed with java9. * A single underscore "_" is an invalid identifier in java9. * Diamond operator for anonymous classes is only allowed with java9. + * Add support for module-info.java. * Andreas Dangel 09/2017 *==================================================================== * Add support for new Java 8 annotation locations. @@ -350,6 +351,11 @@ public class JavaParser { throwParseException("With JDK 9, '_' is a keyword, and may not be used as an identifier!"); } } + private void checkForBadModuleUsage() { + if (jdkVersion < 9) { + throwParseException("Cannot use module declaration when running in JDK inferior to 9 mode!"); + } + } // This is a semantic LOOKAHEAD to determine if we're dealing with an assert // Note that this can't be replaced with a syntactic lookahead @@ -491,6 +497,16 @@ TOKEN : | < VOLATILE: "volatile" > | < WHILE: "while" > | < STRICTFP: "strictfp" > +| < OPEN: "open" > +| < MODULE: "module" > +| < REQUIRES: "requires" > +| < TRANSITIVE: "transitive" > +| < EXPORTS: "exports" > +| < OPENS: "opens" > +| < TO: "to" > +| < USES: "uses" > +| < PROVIDES: "provides" > +| < WITH: "with" > } /* LITERALS */ @@ -1294,7 +1310,8 @@ ASTCompilationUnit CompilationUnit() : { [ LOOKAHEAD( ( Annotation() )* "package" ) PackageDeclaration() ( EmptyStatement() )* ] ( ImportDeclaration() ( EmptyStatement() )* )* - ( TypeDeclaration() ( EmptyStatement() )* )* + ( LOOKAHEAD(2) TypeDeclaration() ( EmptyStatement() )* )* + [ ModuleDeclaration() ( EmptyStatement() )* ] ( < "\u001a" > )? ( < "~[]" > )? @@ -2380,3 +2397,41 @@ void DefaultValue(): { "default" MemberValue() } + +void ModuleDeclaration(): +{ + StringBuilder s = new StringBuilder(); + Token t; + checkForBadModuleUsage(); +} +{ + ( Annotation() )* ["open" {jjtThis.setOpen(true);}] "module" + t= { s.append(t.image); } + ( "." t= { s.append('.').append(t.image); } )* { jjtThis.setImage(s.toString()); } + "{" (ModuleDirective())* "}" +} + +void ModuleDirective(): +{} +{ + ( "requires" { jjtThis.setType(ASTModuleDirective.DirectiveType.REQUIRES); } + ("transitive" { jjtThis.setRequiresModifier(ASTModuleDirective.RequiresModifier.TRANSITIVE); } | + "static" { jjtThis.setRequiresModifier(ASTModuleDirective.RequiresModifier.STATIC); } )? + ModuleName() ";" ) + | ( "exports" { jjtThis.setType(ASTModuleDirective.DirectiveType.EXPORTS); } Name() [ "to" ModuleName() ("," ModuleName())*] ";" ) + | ( "opens" { jjtThis.setType(ASTModuleDirective.DirectiveType.OPENS); } Name() [ "to" ModuleName() ("," ModuleName())*] ";" ) + | ( "uses" { jjtThis.setType(ASTModuleDirective.DirectiveType.USES); } Name() ";" ) + | ( "provides" { jjtThis.setType(ASTModuleDirective.DirectiveType.PROVIDES); } Name() "with" Name() ("," Name() )* ";" ) +} + +// Similar to Name() +void ModuleName(): +{ + StringBuilder s = new StringBuilder(); + Token t; +} +{ + t= { s.append(t.image); } + ( "." t= {s.append('.').append(t.image);} )* + {jjtThis.setImage(s.toString());} +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTModuleDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTModuleDeclaration.java new file mode 100644 index 0000000000..06919eb991 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTModuleDeclaration.java @@ -0,0 +1,33 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +/* Generated By:JJTree: Do not edit this line. ASTModuleDeclaration.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=true,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=AST,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package net.sourceforge.pmd.lang.java.ast; + +public class ASTModuleDeclaration extends AbstractJavaNode { + private boolean open; + + public ASTModuleDeclaration(int id) { + super(id); + } + + public ASTModuleDeclaration(JavaParser p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(JavaParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void setOpen(boolean open) { + this.open = open; + } + + public boolean isOpen() { + return open; + } +} +/* JavaCC - OriginalChecksum=752bbec72a6d0d96c4c69a2d08c73614 (do not edit this line) */ diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTModuleDirective.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTModuleDirective.java new file mode 100644 index 0000000000..b2367336f0 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTModuleDirective.java @@ -0,0 +1,50 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +/* Generated By:JJTree: Do not edit this line. ASTModuleDirective.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=true,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=AST,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ +package net.sourceforge.pmd.lang.java.ast; + +public class ASTModuleDirective extends AbstractJavaNode { + public enum DirectiveType { + REQUIRES, EXPORTS, OPENS, USES, PROVIDES; + } + public enum RequiresModifier { + STATIC, TRANSITIVE; + } + + private DirectiveType type; + private RequiresModifier requiresModifier; + + public ASTModuleDirective(int id) { + super(id); + } + + public ASTModuleDirective(JavaParser p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(JavaParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void setType(DirectiveType type) { + this.type = type; + } + public String getType() { + return String.valueOf(type); + } + + public void setRequiresModifier(RequiresModifier requiresModifier) { + this.requiresModifier = requiresModifier; + } + public String getRequiresModifier() { + return requiresModifier == null ? null : requiresModifier.name(); + } +} +/* + * JavaCC - OriginalChecksum=93c74930e5df0269e81ce18b4efa6378 (do not edit this + * line) + */ diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTModuleName.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTModuleName.java new file mode 100644 index 0000000000..7970817064 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTModuleName.java @@ -0,0 +1,27 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +/* Generated By:JJTree: Do not edit this line. ASTModuleName.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=true,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=AST,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ + +package net.sourceforge.pmd.lang.java.ast; + +public class ASTModuleName extends AbstractJavaNode { + public ASTModuleName(int id) { + super(id); + } + + public ASTModuleName(JavaParser p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + public Object jjtAccept(JavaParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* + * JavaCC - OriginalChecksum=7be9235079394543d4574d840ebb5235 (do not edit this + * line) + */ diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/DumpFacade.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/DumpFacade.java index 66fef23b38..e392d4bf53 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/DumpFacade.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/DumpFacade.java @@ -192,6 +192,12 @@ public class DumpFacade extends JavaParserVisitorAdapter { if (((ASTTryStatement) node).hasFinally()) { extras.add("has finally"); } + } else if (node instanceof ASTModuleDirective) { + ASTModuleDirective directive = (ASTModuleDirective)node; + extras.add(directive.getType()); + if (directive.getRequiresModifier() != null) { + extras.add(directive.getRequiresModifier()); + } } // Output image and extras diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserDecoratedVisitor.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserDecoratedVisitor.java index a7d4c7d822..e6bd5306e2 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserDecoratedVisitor.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserDecoratedVisitor.java @@ -838,4 +838,23 @@ public class JavaParserDecoratedVisitor implements JavaParserVisitor { visitor.visit(node, data); return visit((JavaNode) node, data); } + + + @Override + public Object visit(ASTModuleDeclaration node, Object data) { + visitor.visit(node, data); + return visit((JavaNode) node, data); + } + + @Override + public Object visit(ASTModuleDirective node, Object data) { + visitor.visit(node, data); + return visit((JavaNode) node, data); + } + + @Override + public Object visit(ASTModuleName node, Object data) { + visitor.visit(node, data); + return visit((JavaNode) node, data); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserVisitorAdapter.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserVisitorAdapter.java index e42836fa57..705ff00680 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserVisitorAdapter.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserVisitorAdapter.java @@ -461,4 +461,16 @@ public class JavaParserVisitorAdapter implements JavaParserVisitor { public Object visit(ASTMethodReference node, Object data) { return visit((JavaNode) node, data); } + + public Object visit(ASTModuleDeclaration node, Object data) { + return visit((JavaNode) node, data); + } + + public Object visit(ASTModuleDirective node, Object data) { + return visit((JavaNode) node, data); + } + + public Object visit(ASTModuleName node, Object data) { + return visit((JavaNode) node, data); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserVisitorDecorator.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserVisitorDecorator.java index 386d519c54..fd64ffae44 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserVisitorDecorator.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserVisitorDecorator.java @@ -706,4 +706,22 @@ public class JavaParserVisitorDecorator implements JavaParserControllessVisitor public Object visit(ASTMethodReference node, Object data) { return visitor.visit(node, data); } + + + @Override + public Object visit(ASTModuleDeclaration node, Object data) { + return visitor.visit(node, data); + } + + + @Override + public Object visit(ASTModuleDirective node, Object data) { + return visitor.visit(node, data); + } + + + @Override + public Object visit(ASTModuleName node, Object data) { + return visitor.visit(node, data); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJavaRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJavaRule.java index 637ef9fd08..3266a540c6 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJavaRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJavaRule.java @@ -527,4 +527,16 @@ public abstract class AbstractJavaRule extends AbstractRule implements JavaParse public Object visit(ASTMethodReference node, Object data) { return visit((JavaNode) node, data); } + + public Object visit(ASTModuleDeclaration node, Object data) { + return visit((JavaNode) node, data); + } + + public Object visit(ASTModuleDirective node, Object data) { + return visit((JavaNode) node, data); + } + + public Object visit(ASTModuleName node, Object data) { + return visit((JavaNode) node, data); + } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTModuleDeclarationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTModuleDeclarationTest.java new file mode 100644 index 0000000000..32cb4886b5 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTModuleDeclarationTest.java @@ -0,0 +1,79 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.ast; + +import static net.sourceforge.pmd.lang.java.ParserTstUtil.parseJava9; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.junit.Test; + +public class ASTModuleDeclarationTest { + private static String loadSource(String name) { + try { + return IOUtils.toString(ASTModuleDeclarationTest.class.getResourceAsStream("jdkversiontests/" + name), + StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Test + public final void jdk9ModuleInfo() { + ASTCompilationUnit ast = parseJava9(loadSource("jdk9_module_info.java")); + List modules = ast.findDescendantsOfType(ASTModuleDeclaration.class); + assertEquals(1, modules.size()); + ASTModuleDeclaration module = modules.get(0); + assertTrue(module.isOpen()); + assertEquals("com.example.foo", module.getImage()); + assertEquals(7, module.jjtGetNumChildren()); + List directives = module.findChildrenOfType(ASTModuleDirective.class); + assertEquals(7, directives.size()); + + // requires com.example.foo.http; + assertEquals(ASTModuleDirective.DirectiveType.REQUIRES, directives.get(0).getType()); + assertNull(directives.get(0).getRequiresModifier()); + assertEquals("com.example.foo.http", directives.get(0).getFirstChildOfType(ASTModuleName.class).getImage()); + + // requires java.logging; + assertEquals(ASTModuleDirective.DirectiveType.REQUIRES, directives.get(1).getType()); + assertNull(directives.get(1).getRequiresModifier()); + assertEquals("java.logging", directives.get(1).getFirstChildOfType(ASTModuleName.class).getImage()); + + // requires transitive com.example.foo.network; + assertEquals(ASTModuleDirective.DirectiveType.REQUIRES, directives.get(2).getType()); + assertEquals(ASTModuleDirective.RequiresModifier.TRANSITIVE, directives.get(2).getRequiresModifier()); + assertEquals("com.example.foo.network", directives.get(2).getFirstChildOfType(ASTModuleName.class).getImage()); + + // exports com.example.foo.bar; + assertEquals(ASTModuleDirective.DirectiveType.EXPORTS, directives.get(3).getType()); + assertNull(directives.get(3).getRequiresModifier()); + assertEquals("com.example.foo.bar", directives.get(3).getFirstChildOfType(ASTName.class).getImage()); + + // exports com.example.foo.internal to com.example.foo.probe; + assertEquals(ASTModuleDirective.DirectiveType.EXPORTS, directives.get(4).getType()); + assertNull(directives.get(4).getRequiresModifier()); + assertEquals("com.example.foo.internal", directives.get(4).getFirstChildOfType(ASTName.class).getImage()); + assertEquals("com.example.foo.probe", directives.get(4).getFirstChildOfType(ASTModuleName.class).getImage()); + + // uses com.example.foo.spi.Intf; + assertEquals(ASTModuleDirective.DirectiveType.USES, directives.get(5).getType()); + assertNull(directives.get(5).getRequiresModifier()); + assertEquals("com.example.foo.spi.Intf", directives.get(5).getFirstChildOfType(ASTName.class).getImage()); + + // provides com.example.foo.spi.Intf with com.example.foo.Impl; + assertEquals(ASTModuleDirective.DirectiveType.PROVIDES, directives.get(6).getType()); + assertNull(directives.get(6).getRequiresModifier()); + assertEquals("com.example.foo.spi.Intf", directives.get(6).getFirstChildOfType(ASTName.class).getImage()); + assertEquals("com.example.foo.Impl", directives.get(6).findChildrenOfType(ASTName.class).get(1).getImage()); + } + +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/JDKVersionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/JDKVersionTest.java index 861b9215b8..da519826be 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/JDKVersionTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/JDKVersionTest.java @@ -247,4 +247,14 @@ public class JDKVersionTest { public final void jdk9AnonymousDiamond() { parseJava9(loadSource("jdk9_anonymous_diamond.java")); } + + @Test(expected = ParseException.class) + public final void jdk9ModuleInfoInJava8() { + parseJava18(loadSource("jdk9_module_info.java")); + } + + @Test + public final void jdk9ModuleInfo() { + parseJava9(loadSource("jdk9_module_info.java")); + } } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/jdk9_module_info.java b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/jdk9_module_info.java new file mode 100644 index 0000000000..891a78fe51 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/jdk9_module_info.java @@ -0,0 +1,15 @@ +/* + * See ยง7.7 Module Declarations in JLS + */ +open module com.example.foo { + requires com.example.foo.http; + requires java.logging; + requires transitive com.example.foo.network; + + exports com.example.foo.bar; + exports com.example.foo.internal to com.example.foo.probe; + + uses com.example.foo.spi.Intf; + + provides com.example.foo.spi.Intf with com.example.foo.Impl; +} \ No newline at end of file