diff --git a/pmd-java/etc/grammar/Java.jjt b/pmd-java/etc/grammar/Java.jjt index 8f67174ca1..3611c2057e 100644 --- a/pmd-java/etc/grammar/Java.jjt +++ b/pmd-java/etc/grammar/Java.jjt @@ -1131,11 +1131,23 @@ ASTCompilationUnit CompilationUnit() : { [ LOOKAHEAD( ( Annotation() )* "package" ) PackageDeclaration() ( EmptyDeclaration() )* ] ( ImportDeclaration() ( EmptyDeclaration() )* )* + + // ModularCompilationUnit: // the module decl lookahead needs to be before the type declaration branch, // looking for annotations + "open" | "module" will fail faster if it's *not* // a module (most common case) [ LOOKAHEAD(ModuleDeclLahead()) ModuleDeclaration() ( EmptyDeclaration() )* ] + + // OrdinaryCompilationUnit: -> TopLevelClassOrInterfaceDeclaration ( TypeDeclaration() ( EmptyDeclaration() )* )* + + // UnnamedClassCompilationUnit: + [ + ( LOOKAHEAD(3) ClassMemberDeclarationNoMethod() )* + ModifierList() MethodDeclaration() + ( ClassOrInterfaceBodyDeclaration() )* + ] + { jjtThis.setComments(token_source.comments); @@ -1143,6 +1155,20 @@ ASTCompilationUnit CompilationUnit() : } } +// see ClassOrInterfaceBodyDeclaration() +void ClassMemberDeclarationNoMethod() #void: +{} +{ + ModifierList() + ( LOOKAHEAD(3) ClassOrInterfaceDeclaration() + | LOOKAHEAD({isKeyword("enum")}) EnumDeclaration() + | LOOKAHEAD({isKeyword("record")}) RecordDeclaration() + | LOOKAHEAD( [ TypeParameters() ] "(" ) ConstructorDeclaration() + | LOOKAHEAD( Type() (AnnotationList() "[" "]")* ( "," | "=" | ";" ) ) FieldDeclaration() + | LOOKAHEAD(2) AnnotationTypeDeclaration() + ) +} + private void ModuleDeclLahead() #void: {} { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTCompilationUnit.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTCompilationUnit.java index 0dc06782a4..33fcb2e24b 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTCompilationUnit.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTCompilationUnit.java @@ -9,6 +9,7 @@ import java.util.List; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import net.sourceforge.pmd.annotation.Experimental; import net.sourceforge.pmd.lang.ast.AstInfo; import net.sourceforge.pmd.lang.ast.NodeStream; import net.sourceforge.pmd.lang.ast.RootNode; @@ -16,6 +17,7 @@ import net.sourceforge.pmd.lang.ast.impl.GenericNode; import net.sourceforge.pmd.lang.java.symbols.table.JSymbolTable; import net.sourceforge.pmd.lang.java.types.TypeSystem; import net.sourceforge.pmd.lang.java.types.ast.LazyTypeResolver; +import net.sourceforge.pmd.lang.rule.xpath.NoAttribute; /** @@ -23,7 +25,8 @@ import net.sourceforge.pmd.lang.java.types.ast.LazyTypeResolver; * *
  *
- * CompilationUnit ::= RegularCompilationUnit
+ * CompilationUnit ::= OrdinaryCompilationUnit
+ *                   | UnnamedClassCompilationUnit
  *                   | ModularCompilationUnit
  *
  * RegularCompilationUnit ::=
@@ -31,11 +34,20 @@ import net.sourceforge.pmd.lang.java.types.ast.LazyTypeResolver;
  *   {@linkplain ASTImportDeclaration ImportDeclaration}*
  *   {@linkplain ASTAnyTypeDeclaration TypeDeclaration}*
  *
+ * UnnamedClassCompilationUnit ::=
+ *   {@linkplain ASTImportDeclaration ImportDeclaration}*
+ *   {@linkplain ASTFieldDeclaration FieldDeclaration}*
+ *   {@linkplain ASTMethodDeclaration MethodDeclaration}
+ *   {@linkplain ASTBodyDeclaration BodyDeclaration}*
+ *
  * ModularCompilationUnit ::=
  *   {@linkplain ASTImportDeclaration ImportDeclaration}*
  *   {@linkplain ASTModuleDeclaration ModuleDeclaration}
  *
  * 
+ * + * @see JEP 445: Unnamed Classes and Instance Main Methods (Preview) (Java 21) + * @see #isUnnamedClass() */ public final class ASTCompilationUnit extends AbstractJavaNode implements JavaNode, GenericNode, RootNode { @@ -126,4 +138,9 @@ public final class ASTCompilationUnit extends AbstractJavaNode implements JavaNo return lazyTypeResolver; } + @Experimental + @NoAttribute + public boolean isUnnamedClass() { + return children(ASTMethodDeclaration.class).nonEmpty(); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java index 3a83204420..e5c8be4514 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java @@ -166,6 +166,20 @@ public final class ASTMethodDeclaration extends AbstractMethodOrConstructorDecla && "main".equals(this.getName()) && this.isVoid() && this.getArity() == 1 - && TypeTestUtil.isExactlyA(String[].class, this.getFormalParameters().get(0)); + && TypeTestUtil.isExactlyA(String[].class, this.getFormalParameters().get(0)) + || isMainMethodUnnamedClass(); + } + + /** + * With JEP 445 (Java 21 Preview) the main method does not need to be static anymore and + * does not need to be public or have a formal parameter. + */ + private boolean isMainMethodUnnamedClass() { + return this.getRoot().isUnnamedClass() + && "main".equals(this.getName()) + && !this.hasModifiers(JModifier.PRIVATE) + && this.isVoid() + && (this.getArity() == 0 + || this.getArity() == 1 && TypeTestUtil.isExactlyA(String[].class, this.getFormalParameters().get(0))); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTModifierList.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTModifierList.java index 5868070c64..d7b2d0ae92 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTModifierList.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTModifierList.java @@ -206,7 +206,8 @@ public final class ASTModifierList extends AbstractJavaNode { @Override public Void visit(ASTFieldDeclaration node, Set effective) { - if (node.getEnclosingType().isInterface()) { + ASTAnyTypeDeclaration enclosingType = node.getEnclosingType(); + if (enclosingType != null && enclosingType.isInterface()) { effective.add(PUBLIC); effective.add(STATIC); effective.add(FINAL); @@ -266,8 +267,8 @@ public final class ASTModifierList extends AbstractJavaNode { @Override public Void visit(ASTMethodDeclaration node, Set effective) { - - if (node.getEnclosingType().isInterface()) { + ASTAnyTypeDeclaration enclosingType = node.getEnclosingType(); + if (enclosingType != null && enclosingType.isInterface()) { Set declared = node.getModifiers().explicitModifiers; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/internal/JavaAstUtils.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/internal/JavaAstUtils.java index 5ec7cdc9cc..d9e0502f5e 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/internal/JavaAstUtils.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/internal/JavaAstUtils.java @@ -151,6 +151,10 @@ public final class JavaAstUtils { } public static boolean hasField(ASTAnyTypeDeclaration node, String name) { + if (node == null) { + return false; + } + for (JFieldSymbol f : node.getSymbol().getDeclaredFields()) { String fname = f.getSimpleName(); if (fname.startsWith("m_") || fname.startsWith("_")) { @@ -445,7 +449,7 @@ public final class JavaAstUtils { } public static boolean hasAnyAnnotation(Annotatable node, Collection qualifiedNames) { - return qualifiedNames.stream().anyMatch(node::isAnnotationPresent); + return node != null && qualifiedNames.stream().anyMatch(node::isAnnotationPresent); } /** @@ -586,8 +590,10 @@ public final class JavaAstUtils { if (usage instanceof ASTVariableAccess) { return !Modifier.isStatic(((JFieldSymbol) symbol).getModifiers()); } else if (usage instanceof ASTFieldAccess) { - return Objects.equals(((JFieldSymbol) symbol).getEnclosingClass(), - usage.getEnclosingType().getSymbol()); + if (usage.getEnclosingType() != null) { + return Objects.equals(((JFieldSymbol) symbol).getEnclosingClass(), + usage.getEnclosingType().getSymbol()); + } } return false; } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/internal/LanguageLevelChecker.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/internal/LanguageLevelChecker.java index 6092ed819e..5dffb20b01 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/internal/LanguageLevelChecker.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/internal/LanguageLevelChecker.java @@ -17,6 +17,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTAssertStatement; import net.sourceforge.pmd.lang.java.ast.ASTCastExpression; import net.sourceforge.pmd.lang.java.ast.ASTCatchClause; +import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTConstructorCall; import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTForeachStatement; @@ -86,7 +87,7 @@ public class LanguageLevelChecker { public void check(JavaNode node) { T accumulator = reportingStrategy.createAccumulator(); - node.descendants(JavaNode.class).crossFindBoundaries().forEach(n -> n.acceptVisitor(visitor, accumulator)); + node.descendantsOrSelf().crossFindBoundaries().filterIs(JavaNode.class).forEach(n -> n.acceptVisitor(visitor, accumulator)); reportingStrategy.done(accumulator); } @@ -178,6 +179,12 @@ public class LanguageLevelChecker { */ UNNAMED_PATTERNS_AND_VARIABLES(21, 21, false), + /** + * Unnamed Classes and Instance Main Methods + * @see JEP 445: Unnamed Classes and Instance Main Methods (Preview) (Java 21) + */ + UNNAMED_CLASSES(21, 21, false), + ; // SUPPRESS CHECKSTYLE enum trailing semi is awesome @@ -409,6 +416,14 @@ public class LanguageLevelChecker { return null; } + @Override + public Void visit(ASTCompilationUnit node, T data) { + if (node.isUnnamedClass()) { + check(node, PreviewFeature.UNNAMED_CLASSES, data); + } + return null; + } + @Override public Void visit(ASTStringLiteral node, T data) { if (node.isStringLiteral() && SPACE_ESCAPE_PATTERN.matcher(node.getImage()).find()) { @@ -540,7 +555,7 @@ public class LanguageLevelChecker { check(node, RegularLanguageFeature.DEFAULT_METHODS, data); } - if (node.isPrivate() && node.getEnclosingType().isInterface()) { + if (node.isPrivate() && node.getEnclosingType() != null && node.getEnclosingType().isInterface()) { check(node, RegularLanguageFeature.PRIVATE_METHODS_IN_INTERFACES, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/CommentDefaultAccessModifierRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/CommentDefaultAccessModifierRule.java index 02e93d625c..5d5892bc43 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/CommentDefaultAccessModifierRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/CommentDefaultAccessModifierRule.java @@ -150,7 +150,9 @@ public class CommentDefaultAccessModifierRule extends AbstractJavaRulechainRule return isMissingComment(decl) && isNotIgnored(decl) - && !(decl instanceof ASTFieldDeclaration && enclosing.isAnnotationPresent("lombok.Value")); + && !(decl instanceof ASTFieldDeclaration + && enclosing != null + && enclosing.isAnnotationPresent("lombok.Value")); } private boolean isMissingComment(AccessNode decl) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/CouplingBetweenObjectsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/CouplingBetweenObjectsRule.java index 3ce63e495c..b82dd1a055 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/CouplingBetweenObjectsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/CouplingBetweenObjectsRule.java @@ -127,7 +127,7 @@ public class CouplingBetweenObjectsRule extends AbstractJavaRule { * @return boolean true if variableType is not what we care about */ private boolean ignoreType(ASTType typeNode, JTypeMirror t) { - if (typeNode.getEnclosingType().getSymbol().equals(t.getSymbol())) { + if (typeNode.getEnclosingType() != null && typeNode.getEnclosingType().getSymbol().equals(t.getSymbol())) { return true; } JTypeDeclSymbol symbol = t.getSymbol(); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/TestFrameworksUtil.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/TestFrameworksUtil.java index f3a35f4361..54ae9202f6 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/TestFrameworksUtil.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/TestFrameworksUtil.java @@ -138,7 +138,8 @@ public final class TestFrameworksUtil { * True if this is a {@code TestCase} class for Junit 3. */ public static boolean isJUnit3Class(ASTAnyTypeDeclaration node) { - return node.isRegularClass() + return node != null + && node.isRegularClass() && !node.isNested() && !node.isAbstract() && TypeTestUtil.isA(JUNIT3_CLASS_NAME, node); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/ast/AstSymFactory.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/ast/AstSymFactory.java index 5a4288a5ed..d04865bd63 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/ast/AstSymFactory.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/ast/AstSymFactory.java @@ -6,9 +6,11 @@ package net.sourceforge.pmd.lang.java.symbols.internal.ast; import static net.sourceforge.pmd.util.CollectionUtil.listOf; +import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.internal.JavaAstProcessor; import net.sourceforge.pmd.lang.java.symbols.JClassSymbol; @@ -76,7 +78,7 @@ final class AstSymFactory { return new AstClassSym(klass, this, enclosing); } - - - + JClassSymbol setClassSymbol(@NonNull ASTCompilationUnit compilationUnit) { + return new AstUnnamedClassSym(compilationUnit, this); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/ast/AstSymbolMakerVisitor.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/ast/AstSymbolMakerVisitor.java index 96355c2a72..71b7144f5c 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/ast/AstSymbolMakerVisitor.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/ast/AstSymbolMakerVisitor.java @@ -78,6 +78,17 @@ final class AstSymbolMakerVisitor extends JavaVisitorBase { || node.getParent() instanceof ASTFormalParameter); } + @Override + public Void visit(ASTCompilationUnit node, AstSymFactory data) { + if (node.isUnnamedClass()) { + JClassSymbol sym = data.setClassSymbol(node); + enclosingSymbols.push(sym); + visitChildren(node, data); + enclosingSymbols.pop(); + return null; + } + return super.visit(node, data); + } @Override public Void visitTypeDecl(ASTAnyTypeDeclaration node, AstSymFactory data) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/ast/AstUnnamedClassSym.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/ast/AstUnnamedClassSym.java new file mode 100644 index 0000000000..2da46c218c --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/ast/AstUnnamedClassSym.java @@ -0,0 +1,179 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.symbols.internal.ast; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; +import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; +import net.sourceforge.pmd.lang.java.ast.JModifier; +import net.sourceforge.pmd.lang.java.symbols.JClassSymbol; +import net.sourceforge.pmd.lang.java.symbols.JConstructorSymbol; +import net.sourceforge.pmd.lang.java.symbols.JExecutableSymbol; +import net.sourceforge.pmd.lang.java.symbols.JFieldSymbol; +import net.sourceforge.pmd.lang.java.symbols.JMethodSymbol; +import net.sourceforge.pmd.lang.java.symbols.JTypeDeclSymbol; +import net.sourceforge.pmd.lang.java.types.JClassType; +import net.sourceforge.pmd.lang.java.types.JTypeVar; +import net.sourceforge.pmd.lang.java.types.Substitution; +import net.sourceforge.pmd.lang.java.types.TypeSystem; + +class AstUnnamedClassSym implements JClassSymbol { + private ASTCompilationUnit node; + private final List declaredMethods; + private final List declaredFields; + + AstUnnamedClassSym(ASTCompilationUnit node, AstSymFactory factory) { + this.node = node; + + final List myMethods = new ArrayList<>(); + final List myFields = new ArrayList<>(); + + node.children(ASTMethodDeclaration.class).forEach(dnode -> { + myMethods.add(new AstMethodSym((ASTMethodDeclaration) dnode, factory, this)); + }); + node.children(ASTFieldDeclaration.class).forEach(dnode -> { + for (ASTVariableDeclaratorId varId : dnode.getVarIds()) { + myFields.add(new AstFieldSym(varId, factory, this)); + } + }); + + this.declaredMethods = Collections.unmodifiableList(myMethods); + this.declaredFields = Collections.unmodifiableList(myFields); + } + + @Override + public int getModifiers() { + return JModifier.toReflect(EnumSet.of(JModifier.FINAL)); + } + + @Override + public @Nullable JClassSymbol getEnclosingClass() { + return null; + } + + @Override + public @NonNull String getPackageName() { + return ""; + } + + @Override + public @NonNull String getBinaryName() { + return "UnnamedClass"; + } + + @Override + public @Nullable String getCanonicalName() { + return null; + } + + @Override + public @Nullable JExecutableSymbol getEnclosingMethod() { + return null; + } + + @Override + public List getDeclaredClasses() { + return Collections.emptyList(); + } + + @Override + public List getDeclaredMethods() { + return declaredMethods; + } + + @Override + public List getConstructors() { + return Collections.emptyList(); + } + + @Override + public List getDeclaredFields() { + return declaredFields; + } + + @Override + public List getSuperInterfaceTypes(Substitution substitution) { + return Collections.emptyList(); + } + + @Override + public @Nullable JClassType getSuperclassType(Substitution substitution) { + return null; + } + + @Override + public @Nullable JClassSymbol getSuperclass() { + return null; + } + + @Override + public List getSuperInterfaces() { + return Collections.emptyList(); + } + + @Override + public @Nullable JTypeDeclSymbol getArrayComponent() { + return null; + } + + @Override + public boolean isArray() { + return false; + } + + @Override + public boolean isPrimitive() { + return false; + } + + @Override + public boolean isEnum() { + return false; + } + + @Override + public boolean isRecord() { + return false; + } + + @Override + public boolean isAnnotation() { + return false; + } + + @Override + public boolean isLocalClass() { + return false; + } + + @Override + public boolean isAnonymousClass() { + return false; + } + + @Override + public TypeSystem getTypeSystem() { + return node.getTypeSystem(); + } + + @Override + public @NonNull String getSimpleName() { + return ""; + } + + @Override + public List getTypeParameters() { + return Collections.emptyList(); + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/table/internal/SymbolTableResolver.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/table/internal/SymbolTableResolver.java index 1233f48b0b..b1843f018a 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/table/internal/SymbolTableResolver.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/table/internal/SymbolTableResolver.java @@ -698,6 +698,9 @@ public final class SymbolTableResolver { } private JClassType enclosing() { + if (enclosingType.isEmpty()) { + return null; + } return enclosingType.getFirst().getTypeMirror(); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/ast/MethodInvocMirror.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/ast/MethodInvocMirror.java index 0ac0c6e9a7..4fc9952363 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/ast/MethodInvocMirror.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/ast/MethodInvocMirror.java @@ -4,6 +4,7 @@ package net.sourceforge.pmd.lang.java.types.internal.infer.ast; +import java.util.Collections; import java.util.List; import org.checkerframework.checker.nullness.qual.NonNull; @@ -51,6 +52,8 @@ class MethodInvocMirror extends BaseInvocMirror implements Invoca if (lhs == null) { // already filters accessibility return myNode.getSymbolTable().methods().resolve(getName()); + } else if (myNode.getEnclosingType() == null) { + return Collections.emptyList(); } else { JTypeMirror lhsType; if (lhs instanceof ASTConstructorCall) { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java21PreviewTreeDumpTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java21PreviewTreeDumpTest.java index 5062268b4c..fa649d66b1 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java21PreviewTreeDumpTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java21PreviewTreeDumpTest.java @@ -7,6 +7,8 @@ package net.sourceforge.pmd.lang.java.ast; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -67,4 +69,41 @@ class Java21PreviewTreeDumpTest extends BaseTreeDumpTest { thrown = assertThrows(ParseException.class, () -> java21.parseResource("Jep443_UnnamedPatternsAndVariables2.java")); assertThat(thrown.getMessage(), containsString("Unnamed patterns and variables is a preview feature of JDK 21, you should select your language version accordingly")); } + + @Test + void unnamedClasses1() { + doTest("Jep445_UnnamedClasses1"); + ASTCompilationUnit compilationUnit = java21p.parseResource("Jep445_UnnamedClasses1.java"); + assertTrue(compilationUnit.isUnnamedClass()); + ASTMethodCall methodCall = compilationUnit.descendants(ASTMethodCall.class).first(); + assertNotNull(methodCall.getTypeMirror()); + } + + @Test + void unnamedClasses2() { + doTest("Jep445_UnnamedClasses2"); + } + + @Test + void unnamedClasses3() { + doTest("Jep445_UnnamedClasses3"); + } + + @Test + void unnamedClassesBeforeJava21Preview() { + ParseException thrown = assertThrows(ParseException.class, () -> java21.parseResource("Jep445_UnnamedClasses1.java")); + assertThat(thrown.getMessage(), containsString("Unnamed classes is a preview feature of JDK 21, you should select your language version accordingly")); + } + + @Test + void testOrdinaryCompilationUnit() { + ASTCompilationUnit compilationUnit = java21.parse("public class Foo { public static void main(String[] args) {}}"); + assertFalse(compilationUnit.isUnnamedClass()); + } + + @Test + void testModularCompilationUnit() { + ASTCompilationUnit compilationUnit = java21.parse("module foo {}"); + assertFalse(compilationUnit.isUnnamedClass()); + } } diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java21p/Jep445_UnnamedClasses1.java b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java21p/Jep445_UnnamedClasses1.java new file mode 100644 index 0000000000..5a8891fd2f --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java21p/Jep445_UnnamedClasses1.java @@ -0,0 +1,13 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + + + +/** + * @see JEP 445: Unnamed Classes and Instance Main Methods (Preview) + */ + +void main() { + System.out.println("Hello World"); +} diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java21p/Jep445_UnnamedClasses1.txt b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java21p/Jep445_UnnamedClasses1.txt new file mode 100644 index 0000000000..da108dd22a --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java21p/Jep445_UnnamedClasses1.txt @@ -0,0 +1,13 @@ ++- CompilationUnit[@PackageName = ""] + +- MethodDeclaration[@Abstract = false, @Arity = 0, @EffectiveVisibility = Visibility.V_PACKAGE, @Final = false, @Image = "main", @MainMethod = true, @MethodName = "main", @Name = "main", @Native = false, @Overridden = false, @PackagePrivate = true, @Private = false, @Protected = false, @Public = false, @Static = false, @Strictfp = false, @Synchronized = false, @SyntacticallyAbstract = false, @SyntacticallyFinal = false, @SyntacticallyPublic = false, @SyntacticallyStatic = false, @Transient = false, @Varargs = false, @Visibility = Visibility.V_PACKAGE, @Void = true, @Volatile = false] + +- ModifierList[] + +- VoidType[@ArrayDepth = 0, @ArrayType = false, @ClassOrInterfaceType = false, @PrimitiveType = false, @TypeImage = "void"] + +- FormalParameters[@Empty = true, @Size = 0] + +- Block[@Empty = false, @Size = 1, @containsComment = false] + +- ExpressionStatement[] + +- MethodCall[@CompileTimeConstant = false, @Expression = true, @Image = "println", @MethodName = "println", @ParenthesisDepth = 0, @Parenthesized = false] + +- FieldAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Expression = true, @Image = "out", @Name = "out", @ParenthesisDepth = 0, @Parenthesized = false] + | +- TypeExpression[@CompileTimeConstant = false, @Expression = true, @ParenthesisDepth = 0, @Parenthesized = false] + | +- ClassOrInterfaceType[@ArrayDepth = 0, @ArrayType = false, @ClassOrInterfaceType = true, @FullyQualified = false, @PrimitiveType = false, @ReferenceToClassSameCompilationUnit = false, @SimpleName = "System", @TypeImage = "System"] + +- ArgumentList[@Empty = false, @Size = 1] + +- StringLiteral[@BooleanLiteral = false, @CharLiteral = false, @CompileTimeConstant = true, @ConstValue = "Hello World", @DoubleLiteral = false, @Empty = false, @Expression = true, @FloatLiteral = false, @Image = "\"Hello World\"", @IntLiteral = false, @Length = 11, @LongLiteral = false, @NullLiteral = false, @NumericLiteral = false, @ParenthesisDepth = 0, @Parenthesized = false, @StringLiteral = true, @TextBlock = false] diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java21p/Jep445_UnnamedClasses2.java b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java21p/Jep445_UnnamedClasses2.java new file mode 100644 index 0000000000..49d30a40e1 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java21p/Jep445_UnnamedClasses2.java @@ -0,0 +1,15 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + + + +/** + * @see JEP 445: Unnamed Classes and Instance Main Methods (Preview) + */ + +String greeting() { return "Hello, World!"; } + +void main() { + System.out.println(greeting()); +} diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java21p/Jep445_UnnamedClasses2.txt b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java21p/Jep445_UnnamedClasses2.txt new file mode 100644 index 0000000000..8c36735d06 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java21p/Jep445_UnnamedClasses2.txt @@ -0,0 +1,21 @@ ++- CompilationUnit[@PackageName = ""] + +- MethodDeclaration[@Abstract = false, @Arity = 0, @EffectiveVisibility = Visibility.V_PACKAGE, @Final = false, @Image = "greeting", @MainMethod = false, @MethodName = "greeting", @Name = "greeting", @Native = false, @Overridden = false, @PackagePrivate = true, @Private = false, @Protected = false, @Public = false, @Static = false, @Strictfp = false, @Synchronized = false, @SyntacticallyAbstract = false, @SyntacticallyFinal = false, @SyntacticallyPublic = false, @SyntacticallyStatic = false, @Transient = false, @Varargs = false, @Visibility = Visibility.V_PACKAGE, @Void = false, @Volatile = false] + | +- ModifierList[] + | +- ClassOrInterfaceType[@ArrayDepth = 0, @ArrayType = false, @ClassOrInterfaceType = true, @FullyQualified = false, @PrimitiveType = false, @ReferenceToClassSameCompilationUnit = false, @SimpleName = "String", @TypeImage = "String"] + | +- FormalParameters[@Empty = true, @Size = 0] + | +- Block[@Empty = false, @Size = 1, @containsComment = false] + | +- ReturnStatement[] + | +- StringLiteral[@BooleanLiteral = false, @CharLiteral = false, @CompileTimeConstant = true, @ConstValue = "Hello, World!", @DoubleLiteral = false, @Empty = false, @Expression = true, @FloatLiteral = false, @Image = "\"Hello, World!\"", @IntLiteral = false, @Length = 13, @LongLiteral = false, @NullLiteral = false, @NumericLiteral = false, @ParenthesisDepth = 0, @Parenthesized = false, @StringLiteral = true, @TextBlock = false] + +- MethodDeclaration[@Abstract = false, @Arity = 0, @EffectiveVisibility = Visibility.V_PACKAGE, @Final = false, @Image = "main", @MainMethod = true, @MethodName = "main", @Name = "main", @Native = false, @Overridden = false, @PackagePrivate = true, @Private = false, @Protected = false, @Public = false, @Static = false, @Strictfp = false, @Synchronized = false, @SyntacticallyAbstract = false, @SyntacticallyFinal = false, @SyntacticallyPublic = false, @SyntacticallyStatic = false, @Transient = false, @Varargs = false, @Visibility = Visibility.V_PACKAGE, @Void = true, @Volatile = false] + +- ModifierList[] + +- VoidType[@ArrayDepth = 0, @ArrayType = false, @ClassOrInterfaceType = false, @PrimitiveType = false, @TypeImage = "void"] + +- FormalParameters[@Empty = true, @Size = 0] + +- Block[@Empty = false, @Size = 1, @containsComment = false] + +- ExpressionStatement[] + +- MethodCall[@CompileTimeConstant = false, @Expression = true, @Image = "println", @MethodName = "println", @ParenthesisDepth = 0, @Parenthesized = false] + +- FieldAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Expression = true, @Image = "out", @Name = "out", @ParenthesisDepth = 0, @Parenthesized = false] + | +- TypeExpression[@CompileTimeConstant = false, @Expression = true, @ParenthesisDepth = 0, @Parenthesized = false] + | +- ClassOrInterfaceType[@ArrayDepth = 0, @ArrayType = false, @ClassOrInterfaceType = true, @FullyQualified = false, @PrimitiveType = false, @ReferenceToClassSameCompilationUnit = false, @SimpleName = "System", @TypeImage = "System"] + +- ArgumentList[@Empty = false, @Size = 1] + +- MethodCall[@CompileTimeConstant = false, @Expression = true, @Image = "greeting", @MethodName = "greeting", @ParenthesisDepth = 0, @Parenthesized = false] + +- ArgumentList[@Empty = true, @Size = 0] diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java21p/Jep445_UnnamedClasses3.java b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java21p/Jep445_UnnamedClasses3.java new file mode 100644 index 0000000000..9c4fcae3f7 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java21p/Jep445_UnnamedClasses3.java @@ -0,0 +1,15 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + + + +/** + * @see JEP 445: Unnamed Classes and Instance Main Methods (Preview) + */ + +String greeting = "Hello, World!"; + +void main() { + System.out.println(greeting); +} diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java21p/Jep445_UnnamedClasses3.txt b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java21p/Jep445_UnnamedClasses3.txt new file mode 100644 index 0000000000..be9ebbb6e2 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java21p/Jep445_UnnamedClasses3.txt @@ -0,0 +1,19 @@ ++- CompilationUnit[@PackageName = ""] + +- FieldDeclaration[@Abstract = false, @EffectiveVisibility = Visibility.V_PACKAGE, @Final = false, @Native = false, @PackagePrivate = true, @Private = false, @Protected = false, @Public = false, @Static = false, @Strictfp = false, @Synchronized = false, @SyntacticallyAbstract = false, @SyntacticallyFinal = false, @SyntacticallyPublic = false, @SyntacticallyStatic = false, @Transient = false, @VariableName = "greeting", @Visibility = Visibility.V_PACKAGE, @Volatile = false] + | +- ModifierList[] + | +- ClassOrInterfaceType[@ArrayDepth = 0, @ArrayType = false, @ClassOrInterfaceType = true, @FullyQualified = false, @PrimitiveType = false, @ReferenceToClassSameCompilationUnit = false, @SimpleName = "String", @TypeImage = "String"] + | +- VariableDeclarator[@Initializer = true, @Name = "greeting"] + | +- VariableDeclaratorId[@Abstract = false, @ArrayType = false, @EffectiveVisibility = Visibility.V_PACKAGE, @EnumConstant = false, @ExceptionBlockParameter = false, @Field = true, @Final = false, @ForLoopVariable = false, @ForeachVariable = false, @FormalParameter = false, @Image = "greeting", @LambdaParameter = false, @LocalVariable = false, @Name = "greeting", @Native = false, @PackagePrivate = true, @PatternBinding = false, @Private = false, @Protected = false, @Public = false, @RecordComponent = false, @ResourceDeclaration = false, @Static = false, @Strictfp = false, @Synchronized = false, @SyntacticallyAbstract = false, @SyntacticallyFinal = false, @SyntacticallyPublic = false, @SyntacticallyStatic = false, @Transient = false, @TypeInferred = false, @VariableName = "greeting", @Visibility = Visibility.V_PACKAGE, @Volatile = false] + | +- StringLiteral[@BooleanLiteral = false, @CharLiteral = false, @CompileTimeConstant = true, @ConstValue = "Hello, World!", @DoubleLiteral = false, @Empty = false, @Expression = true, @FloatLiteral = false, @Image = "\"Hello, World!\"", @IntLiteral = false, @Length = 13, @LongLiteral = false, @NullLiteral = false, @NumericLiteral = false, @ParenthesisDepth = 0, @Parenthesized = false, @StringLiteral = true, @TextBlock = false] + +- MethodDeclaration[@Abstract = false, @Arity = 0, @EffectiveVisibility = Visibility.V_PACKAGE, @Final = false, @Image = "main", @MainMethod = true, @MethodName = "main", @Name = "main", @Native = false, @Overridden = false, @PackagePrivate = true, @Private = false, @Protected = false, @Public = false, @Static = false, @Strictfp = false, @Synchronized = false, @SyntacticallyAbstract = false, @SyntacticallyFinal = false, @SyntacticallyPublic = false, @SyntacticallyStatic = false, @Transient = false, @Varargs = false, @Visibility = Visibility.V_PACKAGE, @Void = true, @Volatile = false] + +- ModifierList[] + +- VoidType[@ArrayDepth = 0, @ArrayType = false, @ClassOrInterfaceType = false, @PrimitiveType = false, @TypeImage = "void"] + +- FormalParameters[@Empty = true, @Size = 0] + +- Block[@Empty = false, @Size = 1, @containsComment = false] + +- ExpressionStatement[] + +- MethodCall[@CompileTimeConstant = false, @Expression = true, @Image = "println", @MethodName = "println", @ParenthesisDepth = 0, @Parenthesized = false] + +- FieldAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Expression = true, @Image = "out", @Name = "out", @ParenthesisDepth = 0, @Parenthesized = false] + | +- TypeExpression[@CompileTimeConstant = false, @Expression = true, @ParenthesisDepth = 0, @Parenthesized = false] + | +- ClassOrInterfaceType[@ArrayDepth = 0, @ArrayType = false, @ClassOrInterfaceType = true, @FullyQualified = false, @PrimitiveType = false, @ReferenceToClassSameCompilationUnit = false, @SimpleName = "System", @TypeImage = "System"] + +- ArgumentList[@Empty = false, @Size = 1] + +- VariableAccess[@AccessType = AccessType.READ, @CompileTimeConstant = false, @Expression = true, @Image = "greeting", @Name = "greeting", @ParenthesisDepth = 0, @Parenthesized = false]