From cfa4b5b85cb88b166b0d3f29273abe4719b588f6 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Fri, 22 Sep 2017 12:16:51 +0200 Subject: [PATCH] Add Java9 Language Module, Update grammar * Java8 mode now rejects private methods in interfaces * Java9 mode now rejects "_" as identifier --- pmd-java/etc/grammar/Java.jjt | 32 ++++++++++++++- .../pmd/lang/java/Java19Handler.java | 15 +++++++ .../pmd/lang/java/Java19Parser.java | 28 +++++++++++++ .../pmd/lang/java/JavaLanguageModule.java | 1 + .../pmd/lang/java/ParserTstUtil.java | 10 +++++ .../pmd/lang/java/ast/JDKVersionTest.java | 27 +++++++++++++ .../jdk9_invalid_identifier.java | 15 +++++++ .../jdk9_private_interface_methods.java | 40 +++++++++++++++++++ 8 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java19Handler.java create mode 100644 pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java19Parser.java create mode 100644 pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/jdk9_invalid_identifier.java create mode 100644 pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/jdk9_private_interface_methods.java diff --git a/pmd-java/etc/grammar/Java.jjt b/pmd-java/etc/grammar/Java.jjt index 688acd1cc1..d3720dd64d 100644 --- a/pmd-java/etc/grammar/Java.jjt +++ b/pmd-java/etc/grammar/Java.jjt @@ -1,4 +1,9 @@ /** + * Add support for Java 9 changes + * private interface methods are only allowed with java9, + * a single underscore "_" is an invalid identifier in java9. + * Andreas Dangel 09/2017 + *==================================================================== * Add support for new Java 8 annotation locations. * Bugs #414, #415, #417 * @Snap252 06/2017 @@ -319,6 +324,22 @@ public class JavaParser { throwParseException("Cannot use explicit receiver parameters when running in JDK inferior to 1.8 mode!"); } } + /** + * Keeps track whether we are dealing with an interface or not. Needed since the tree is + * is not fully constructed yet, when we check for private interface methods. + * The flag is updated, if entering ClassOrInterfaceDeclaration and reset when leaving. + */ + private boolean inInterface = false; + private void checkForBadPrivateInterfaceMethod(ASTMethodDeclaration node) { + if (jdkVersion < 9 && inInterface && node.isPrivate()) { + throwParseException("Cannot use private interface methods when running in JDK inferior to 9 mode!"); + } + } + private void checkForBadIdentifier(String image) { + if (jdkVersion >= 9 && "_".equals(image)) { + throwParseException("With JDK 9, '_' is a keyword, and may not be used as an identifier!"); + } + } // 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 @@ -1343,12 +1364,14 @@ jjtThis.setModifiers(modifiers); } { - ( /* See note about this optional final modifier in BlockStatement */ ["final"|"abstract"] "class" | "interface" { jjtThis.setInterface(); } ) + ( /* See note about this optional final modifier in BlockStatement */ + ["final"|"abstract"] "class" | "interface" { jjtThis.setInterface(); inInterface = true; } ) t= { jjtThis.setImage(t.image); } [ TypeParameters() ] [ ExtendsList() ] [ ImplementsList() ] ClassOrInterfaceBody() + { inInterface = false; } // always reset the flag after leaving the node } void ExtendsList(): @@ -1446,6 +1469,7 @@ void ClassOrInterfaceBodyDeclaration(): ";" } + void FieldDeclaration(int modifiers) : {jjtThis.setModifiers(modifiers);} { @@ -1471,6 +1495,7 @@ void VariableDeclaratorId() : { checkForBadAssertUsage(image, "a variable name"); checkForBadEnumUsage(image, "a variable name"); + checkForBadIdentifier(image); jjtThis.setImage( image ); } } @@ -1489,7 +1514,10 @@ void ArrayInitializer() : } void MethodDeclaration(int modifiers) : -{jjtThis.setModifiers(modifiers);} +{ + jjtThis.setModifiers(modifiers); + { checkForBadPrivateInterfaceMethod(jjtThis); } +} { [ TypeParameters() ] (Annotation() {checkForBadTypeAnnotations();})* ResultType() MethodDeclarator() [ "throws" NameList() ] diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java19Handler.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java19Handler.java new file mode 100644 index 0000000000..750a31d643 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java19Handler.java @@ -0,0 +1,15 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java; + +import net.sourceforge.pmd.lang.Parser; +import net.sourceforge.pmd.lang.ParserOptions; + +public class Java19Handler extends AbstractJavaHandler { + + public Parser getParser(ParserOptions parserOptions) { + return new Java19Parser(parserOptions); + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java19Parser.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java19Parser.java new file mode 100644 index 0000000000..3dc38ea3d6 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java19Parser.java @@ -0,0 +1,28 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java; + +import java.io.Reader; + +import net.sourceforge.pmd.lang.ParserOptions; +import net.sourceforge.pmd.lang.java.ast.JavaParser; +import net.sourceforge.pmd.lang.java.ast.ParseException; + +/** + * Adapter for the JavaParser, using Java 1.9 grammar. + */ +public class Java19Parser extends AbstractJavaParser { + + public Java19Parser(ParserOptions parserOptions) { + super(parserOptions); + } + + @Override + protected JavaParser createJavaParser(Reader source) throws ParseException { + JavaParser javaParser = super.createJavaParser(source); + javaParser.setJdkVersion(9); + return javaParser; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaLanguageModule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaLanguageModule.java index 60a5c43d10..ff9a0287ce 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaLanguageModule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaLanguageModule.java @@ -23,6 +23,7 @@ public class JavaLanguageModule extends BaseLanguageModule { addVersion("1.6", new Java16Handler(), false); addVersion("1.7", new Java17Handler(), false); addVersion("1.8", new Java18Handler(), true); + addVersion("9", new Java19Handler(), false); } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ParserTstUtil.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ParserTstUtil.java index 9a45ef1479..b0d3968ebf 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ParserTstUtil.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ParserTstUtil.java @@ -145,6 +145,11 @@ public class ParserTstUtil { return parseJava(getLanguageVersionHandler("1.8"), code); } + /** @see #parseJava(LanguageVersionHandler, String) */ + public static ASTCompilationUnit parseJava9(String code) { + return parseJava(getLanguageVersionHandler("9"), code); + } + /** @see #parseJava(LanguageVersionHandler, String) */ public static ASTCompilationUnit parseJava13(Class source) { return parseJava13(getSourceFromClass(source)); @@ -170,6 +175,11 @@ public class ParserTstUtil { return parseJava18(getSourceFromClass(source)); } + /** @see #parseJava(LanguageVersionHandler, String) */ + public static ASTCompilationUnit parseJava9(Class source) { + return parseJava9(getSourceFromClass(source)); + } + /** @see #parseJava(LanguageVersionHandler, String) */ public static ASTCompilationUnit parseJavaDefaultVersion(String source) { return parseJava(getDefaultLanguageVersionHandler(), source); 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 3e22d5e3d4..019687f12b 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 @@ -8,6 +8,8 @@ import static net.sourceforge.pmd.lang.java.ParserTstUtil.parseJava13; import static net.sourceforge.pmd.lang.java.ParserTstUtil.parseJava14; import static net.sourceforge.pmd.lang.java.ParserTstUtil.parseJava15; import static net.sourceforge.pmd.lang.java.ParserTstUtil.parseJava17; +import static net.sourceforge.pmd.lang.java.ParserTstUtil.parseJava18; +import static net.sourceforge.pmd.lang.java.ParserTstUtil.parseJava9; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -205,4 +207,29 @@ public class JDKVersionTest { public final void testMulticatchWithAnnotations() { parseJava17(loadSource("jdk17_multicatch_with_annotations.java")); } + + @Test(expected = ParseException.class) + public final void jdk9PrivateInterfaceMethodsInJava18() { + parseJava18(loadSource("jdk9_private_interface_methods.java")); + } + + @Test + public final void testPrivateMethods() { + parseJava18("public class Foo { private void bar() { } }"); + } + + @Test + public final void jdk9PrivateInterfaceMethods() { + parseJava9(loadSource("jdk9_private_interface_methods.java")); + } + + @Test + public final void jdk9InvalidIdentifierInJava18() { + parseJava18(loadSource("jdk9_invalid_identifier.java")); + } + + @Test(expected = ParseException.class) + public final void jdk9InvalidIdentifier() { + parseJava9(loadSource("jdk9_invalid_identifier.java")); + } } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/jdk9_invalid_identifier.java b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/jdk9_invalid_identifier.java new file mode 100644 index 0000000000..ce08b1aa74 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/jdk9_invalid_identifier.java @@ -0,0 +1,15 @@ +/** + * Using "_" as an identifier is not allowed anymore with java9. + */ +public class Java9Identifier { + + /* + * see https://bugs.openjdk.java.net/browse/JDK-8061549 + */ + public interface Lambda { + public int a(int _); + public default void t(Lambda l) { + t(_ -> 0); + } + } +} diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/jdk9_private_interface_methods.java b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/jdk9_private_interface_methods.java new file mode 100644 index 0000000000..1f3741458f --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/jdk9_private_interface_methods.java @@ -0,0 +1,40 @@ +/** + * With java9, private methods are allowed in interface. + */ +public class Java9Interface { + + public interface Tool { + void use(); + + default String getName() { + return determineName(); + } + + default String getStaticName() { + return determineNameStatic(); + } + + private String determineName() { + return "unknown:" + this.getClass().getSimpleName(); + } + + private static String determineNameStatic() { + return "unknown:" + Tool.class.getSimpleName(); + } + } + + public static class SampleTool implements Tool { + + public void use() { + if (true) { // force a PMD violation: java-basic/UnconditionalIfStatement + System.out.println("Instance: " + getName()); + System.out.println(" Static: " + getStaticName()); + } + } + } + + public static void main(String... args) { + Tool tool = new SampleTool(); + tool.use(); + } +}