From 77664d9d7f1d4bca02bf7bb58eb6de9a14338fe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Sat, 14 Dec 2019 18:02:49 +0100 Subject: [PATCH] Improve switch grammar --- pmd-java/etc/grammar/Java.jjt | 39 +- .../pmd/lang/java/ast/ASTBlock.java | 2 +- .../pmd/lang/java/ast/ASTExpression.java | 6 +- .../lang/java/ast/ASTSwitchArrowBranch.java | 49 +++ .../pmd/lang/java/ast/ASTSwitchArrowRHS.java | 20 + .../pmd/lang/java/ast/ASTSwitchBranch.java | 26 ++ .../lang/java/ast/ASTSwitchExpression.java | 36 +- .../java/ast/ASTSwitchFallthroughBranch.java | 65 +++ .../pmd/lang/java/ast/ASTSwitchLabel.java | 10 + .../lang/java/ast/ASTSwitchLabeledBlock.java | 36 -- .../java/ast/ASTSwitchLabeledExpression.java | 36 -- .../lang/java/ast/ASTSwitchLabeledRule.java | 29 -- .../ast/ASTSwitchLabeledThrowStatement.java | 36 -- .../pmd/lang/java/ast/ASTSwitchLike.java | 100 +++++ .../pmd/lang/java/ast/ASTSwitchStatement.java | 74 +--- .../pmd/lang/java/ast/ASTThrowStatement.java | 6 +- .../pmd/lang/java/ast/ASTYieldStatement.java | 1 - .../java/ast/SideEffectingVisitorAdapter.java | 4 - .../ast/internal/LanguageLevelChecker.java | 4 +- .../internal/visitors/CycloVisitor.java | 10 +- .../typeresolution/ClassTypeResolver.java | 112 +++-- .../pmd/lang/java/ast/Java12Test.java | 74 ---- .../pmd/lang/java/ast/Java13Test.java | 39 -- .../pmd/lang/java/ast/ParserCornersTest.java | 4 +- .../lang/java/ast/ASTSwitchExpressionTests.kt | 392 ++++++++++-------- .../pmd/lang/java/ast/TestExtensions.kt | 41 ++ .../java13/SwitchExpressions.java | 41 -- .../java13/SwitchExpressionsYield.java | 36 -- 28 files changed, 616 insertions(+), 712 deletions(-) create mode 100644 pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchArrowBranch.java create mode 100644 pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchArrowRHS.java create mode 100644 pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchBranch.java create mode 100644 pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchFallthroughBranch.java delete mode 100644 pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabeledBlock.java delete mode 100644 pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabeledExpression.java delete mode 100644 pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabeledRule.java delete mode 100644 pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabeledThrowStatement.java create mode 100644 pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLike.java delete mode 100644 pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java13/SwitchExpressions.java delete mode 100644 pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java13/SwitchExpressionsYield.java diff --git a/pmd-java/etc/grammar/Java.jjt b/pmd-java/etc/grammar/Java.jjt index c938b8aed5..fca826be2f 100644 --- a/pmd-java/etc/grammar/Java.jjt +++ b/pmd-java/etc/grammar/Java.jjt @@ -2782,49 +2782,36 @@ void SwitchStatement(): "switch" "(" Expression() ")" SwitchBlock() } -void SwitchBlock() #void : +void SwitchBlock() #void: {} { "{" ( - SwitchLabel() - ( - "->" SwitchLabeledRulePart() (SwitchLabeledRule())* - | - ":" (LOOKAHEAD(2) SwitchLabel() ":")* - // the lookahead is to prevent choosing BlockStatement when the token is "default", - // which could happen as local class declarations accept the "default" modifier. - (LOOKAHEAD({shouldStartStatementInSwitch()}) BlockStatement())* - (SwitchLabeledStatementGroup())* - ) - )? + LOOKAHEAD(SwitchLabel() ":") (SwitchFallthroughBranch())+ + | (SwitchArrowBranch())* + ) "}" } -void SwitchLabeledRule() #void : +void SwitchArrowLahead() #void: {} { - SwitchLabel() "->" SwitchLabeledRulePart() + SwitchLabel() "->" } -void SwitchLabeledRulePart() #void: +void SwitchArrowBranch(): {} { - ( Expression() ";" ) #SwitchLabeledExpression(2) - | - ( Block() ) #SwitchLabeledBlock(2) - | - ( ThrowStatement() ) #SwitchLabeledThrowStatement(2) + SwitchLabel() "->" (Expression() ";" | Block() | ThrowStatement()) } -// For PMD 7, make this a real node to group the labels + statements -void SwitchLabeledStatementGroup() #void: +void SwitchFallthroughBranch(): {} { - (LOOKAHEAD(2) SwitchLabel() ":")+ - // the lookahead is to prevent choosing BlockStatement when the token is "default", - // which could happen as local class declarations accept the "default" modifier. - (LOOKAHEAD({shouldStartStatementInSwitch()}) BlockStatement() )* + SwitchLabel() ":" + // the lookahead is to prevent choosing BlockStatement when the token is "default", + // which could happen as local class declarations accept the "default" modifier. + (LOOKAHEAD({shouldStartStatementInSwitch()}) BlockStatement() )* } void SwitchLabel() : diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTBlock.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTBlock.java index 3291cdb5e8..450f613e28 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTBlock.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTBlock.java @@ -16,7 +16,7 @@ import java.util.Iterator; * * */ -public final class ASTBlock extends AbstractStatement implements Iterable { +public final class ASTBlock extends AbstractStatement implements Iterable, ASTSwitchArrowRHS { private boolean containsComment; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTExpression.java index f8ba6c0b57..40b8184e94 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTExpression.java @@ -36,7 +36,11 @@ import org.checkerframework.checker.nullness.qual.Nullable; * * */ -public interface ASTExpression extends JavaNode, TypeNode, ASTMemberValue { +public interface ASTExpression + extends JavaNode, + TypeNode, + ASTMemberValue, + ASTSwitchArrowRHS { /** * Always returns true. This is to allow XPath queries diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchArrowBranch.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchArrowBranch.java new file mode 100644 index 0000000000..91261e4a1a --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchArrowBranch.java @@ -0,0 +1,49 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.ast; + +/** + * A non-fallthrough switch rule, introduced with switch expressions. + * See {@link ASTSwitchLike}. + * + *
+ *
+ * SwitchLabeledExpression ::= {@link ASTSwitchLabel SwitchLabel} "->" {@link ASTSwitchArrowRHS SwitchArrowRHS}
+ *
+ * 
+ */ +public final class ASTSwitchArrowBranch extends AbstractJavaNode implements LeftRecursiveNode { + + ASTSwitchArrowBranch(int id) { + super(id); + } + + ASTSwitchArrowBranch(JavaParser p, int id) { + super(p, id); + } + + @Override + public Object jjtAccept(JavaParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + @Override + public void jjtAccept(SideEffectingVisitor visitor, T data) { + visitor.visit(this, data); + } + + + /** Returns the label, which may be compound. */ + public ASTSwitchLabel getLabel() { + return (ASTSwitchLabel) getFirstChild(); + } + + /** Returns the right hand side of the arrow. */ + public ASTSwitchArrowRHS getRightHandSide() { + return (ASTSwitchArrowRHS) getLastChild(); + } + + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchArrowRHS.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchArrowRHS.java new file mode 100644 index 0000000000..191984c2c3 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchArrowRHS.java @@ -0,0 +1,20 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.ast; + +/** + * A node that can appear as the right-hand-side of a {@link ASTSwitchArrowBranch SwitchArrowRule}. + * + *
+ *
+ * SwitchArrowRightHandSide ::= {@link ASTExpression Expression}
+ *                            | {@link ASTBlock Block}
+ *                            | {@link ASTThrowStatement ThrowStatement}
+ *
+ * 
+ */ +public interface ASTSwitchArrowRHS extends JavaNode { + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchBranch.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchBranch.java new file mode 100644 index 0000000000..be32f050ce --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchBranch.java @@ -0,0 +1,26 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.ast; + +/** + * A branch of a {@link ASTSwitchLike SwitchLike}. + * + *
+ *
+ * SwitchBranch ::= {@link ASTSwitchArrowBranch SwitchArrowBranch}
+ *                | {@link ASTSwitchFallthroughBranch FallthroughBranch}
+ *
+ * 
+ */ +public interface ASTSwitchBranch extends JavaNode { + + /** + * Returns the label, which may be compound. + */ + default ASTSwitchLabel getLabel() { + return (ASTSwitchLabel) getFirstChild(); + } + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchExpression.java index 37078aa252..03ab367c49 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchExpression.java @@ -6,32 +6,19 @@ package net.sourceforge.pmd.lang.java.ast; /** * A switch expression, as introduced in Java 12. This node only occurs - * in the contexts where an expression is expected. In particular, if + * in the contexts where an expression is expected. In particular, * switch constructs occurring in statement position are parsed as a * {@linkplain ASTSwitchStatement SwitchStatement}, and not a * {@link ASTSwitchExpression SwitchExpression} within a - * {@link ASTStatementExpression StatementExpression}. That is - * because switch statements are not required to be exhaustive, contrary + * {@link ASTExpressionStatement ExpressionStatement}. That is because + * switch statements are not required to be exhaustive, contrary * to switch expressions. * - *

Their syntax is identical though. - * - *

TODO Do we group the statements of a SwitchNormalBlock ? Do we unify - * their interface ? SwitchStatement implements Iterator<SwitchLabel> - * which seems shitty tbh. - * - *

- *
- * SwitchExpression  ::= "switch" "(" {@link ASTExpression Expression} ")" SwitchBlock
- *
- * SwitchBlock       ::= SwitchArrowBlock | SwitchNormalBlock
- *
- * SwitchArrowBlock  ::=  "{" ( {@link ASTSwitchLabeledRule SwitchLabeledRule} )* "}"
- * SwitchNormalBlock ::=  "{" ( {@linkplain ASTSwitchLabel SwitchLabel} {@linkplain ASTBlockStatement BlockStatement}* )* "}"
- *
- * 
+ *

Their syntax is identical though, and described on {@link ASTSwitchLike}. */ -public final class ASTSwitchExpression extends AbstractJavaExpr implements ASTExpression { +public final class ASTSwitchExpression extends AbstractJavaExpr + implements ASTExpression, + ASTSwitchLike { ASTSwitchExpression(int id) { super(id); @@ -52,13 +39,4 @@ public final class ASTSwitchExpression extends AbstractJavaExpr implements ASTEx visitor.visit(this, data); } - - /** - * Gets the expression tested by this switch. - * This is the expression between the parentheses. - */ - public ASTExpression getTestedExpression() { - return (ASTExpression) jjtGetChild(0); - } - } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchFallthroughBranch.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchFallthroughBranch.java new file mode 100644 index 0000000000..bbaf2ebff8 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchFallthroughBranch.java @@ -0,0 +1,65 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.ast; + +import java.util.List; + +/** + * A fallthrough switch branch. This contains exactly one label, and zero + * or more statements. Fallthrough must be handled by looking at the siblings. + * For example, in the following, the branch for {@code case 1:} has no statements, + * while the branch for {@code case 2:} has two. + * + *

{@code
+ *
+ * switch (foo) {
+ *  case 1:
+ *  case 2:
+ *      do1Or2();
+ *      break;
+ *  default:
+ *      doDefault();
+ *      break;
+ * }
+ *
+ * }
+ * + * + *
+ *
+ * SwitchFallthroughBranch ::= {@link ASTSwitchLabel SwitchLabel} ":" {@link ASTStatement Statement}*
+ *
+ * 
+ */ +public final class ASTSwitchFallthroughBranch extends AbstractJavaNode + implements LeftRecursiveNode, ASTSwitchBranch { + + ASTSwitchFallthroughBranch(int id) { + super(id); + } + + ASTSwitchFallthroughBranch(JavaParser p, int id) { + super(p, id); + } + + @Override + public Object jjtAccept(JavaParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + + @Override + public void jjtAccept(SideEffectingVisitor visitor, T data) { + visitor.visit(this, data); + } + + /** + * Returns the list of statements dominated by the labels. This list is possibly empty. + */ + public List getStatements() { + return findChildrenOfType(ASTStatement.class); + } + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabel.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabel.java index 0fe46a3f6b..e70bcd0e90 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabel.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabel.java @@ -5,6 +5,7 @@ package net.sourceforge.pmd.lang.java.ast; import java.util.Iterator; +import java.util.List; /** @@ -37,10 +38,19 @@ public final class ASTSwitchLabel extends AbstractJavaNode implements Iterable getExprList() { + return findChildrenOfType(ASTExpression.class); + } + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabeledBlock.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabeledBlock.java deleted file mode 100644 index f7306581a5..0000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabeledBlock.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.ast; - -/** - * One of the {@linkplain ASTSwitchLabeledRule SwitchLabeledRule}s. - * - *
- *
- * SwitchLabeledBlock ::= {@link ASTSwitchLabel SwitchLabel} "->" {@link ASTBlock Block}
- *
- * 
- */ -public final class ASTSwitchLabeledBlock extends AbstractJavaNode implements ASTSwitchLabeledRule { - - ASTSwitchLabeledBlock(int id) { - super(id); - } - - ASTSwitchLabeledBlock(JavaParser p, int id) { - super(p, id); - } - - @Override - public Object jjtAccept(JavaParserVisitor visitor, Object data) { - return visitor.visit(this, data); - } - - - @Override - public void jjtAccept(SideEffectingVisitor visitor, T data) { - visitor.visit(this, data); - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabeledExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabeledExpression.java deleted file mode 100644 index 4e86ff9c4f..0000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabeledExpression.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.ast; - -/** - * One of the {@linkplain ASTSwitchLabeledRule SwitchLabeledRule}s. - * - *
- *
- * SwitchLabeledExpression ::= {@link ASTSwitchLabel SwitchLabel} "->" {@link ASTExpression Expression}
- *
- * 
- */ -public final class ASTSwitchLabeledExpression extends AbstractJavaNode implements ASTSwitchLabeledRule { - - ASTSwitchLabeledExpression(int id) { - super(id); - } - - ASTSwitchLabeledExpression(JavaParser p, int id) { - super(p, id); - } - - @Override - public Object jjtAccept(JavaParserVisitor visitor, Object data) { - return visitor.visit(this, data); - } - - - @Override - public void jjtAccept(SideEffectingVisitor visitor, T data) { - visitor.visit(this, data); - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabeledRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabeledRule.java deleted file mode 100644 index c6fe4ae8d4..0000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabeledRule.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.ast; - -/** - * A non-fallthrough switch case, written with an arrow ({@code ->}) - * instead of a colon. - * - *
- *
- * SwitchLabeledRule ::= {@link ASTSwitchLabeledExpression SwitchLabeledExpression}
- *                     | {@link ASTSwitchLabeledBlock SwitchLabeledBlock}
- *                     | {@link ASTSwitchLabeledThrowStatement SwitchLabeledThrowStatement}
- *
- * 
- */ -public interface ASTSwitchLabeledRule extends JavaNode, LeftRecursiveNode { - // needs to extend LeftRecursiveNode to fit the text range to the children - - /** - * Returns the label of this expression. - */ - default ASTSwitchLabel getLabel() { - return (ASTSwitchLabel) jjtGetChild(0); - } - -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabeledThrowStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabeledThrowStatement.java deleted file mode 100644 index 0c0df528a9..0000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabeledThrowStatement.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.ast; - -/** - * One of the {@linkplain ASTSwitchLabeledRule SwitchLabeledRule}s. - * - *
- *
- * SwitchLabeledThrowStatement ::= {@link ASTSwitchLabel SwitchLabel} "->" {@link ASTThrowStatement ThrowStatement}
- *
- * 
- */ -public final class ASTSwitchLabeledThrowStatement extends AbstractJavaNode implements ASTSwitchLabeledRule { - - ASTSwitchLabeledThrowStatement(int id) { - super(id); - } - - ASTSwitchLabeledThrowStatement(JavaParser p, int id) { - super(p, id); - } - - @Override - public Object jjtAccept(JavaParserVisitor visitor, Object data) { - return visitor.visit(this, data); - } - - - @Override - public void jjtAccept(SideEffectingVisitor visitor, T data) { - visitor.visit(this, data); - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLike.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLike.java new file mode 100644 index 0000000000..dffeeb3cdc --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLike.java @@ -0,0 +1,100 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.ast; + +import java.util.Iterator; +import java.util.Set; + +import org.apache.commons.lang3.EnumUtils; + +import net.sourceforge.pmd.lang.ast.NodeStream; + + +/** + * Common supertype for {@linkplain ASTSwitchStatement switch statements} + * and {@linkplain ASTSwitchExpression switch expressions}. Their grammar + * is identical, and is described below. The difference is that switch + * expressions need to be exhaustive. + * + *
+ *
+ * SwitchLike        ::= {@link ASTSwitchExpression SwitchExpression}
+ *                     | {@link ASTSwitchStatement SwitchStatement}
+ *
+ *                   ::= "switch" "(" {@link ASTExpression Expression} ")" SwitchBlock
+ *
+ * SwitchBlock       ::= SwitchArrowBlock | SwitchNormalBlock
+ *
+ * SwitchArrowBlock  ::= "{" {@link ASTSwitchArrowBranch SwitchArrowBranch}* "}"
+ * SwitchNormalBlock ::= "{" {@linkplain ASTSwitchFallthroughBranch SwitchFallthroughBranch}* "}"
+ *
+ * 
+ */ +public interface ASTSwitchLike extends JavaNode, Iterable { + + /** + * Returns true if this switch has a {@code default} case. + */ + default boolean hasDefaultCase() { + ASTSwitchBranch last = getBranches().last(); + return last != null && last.getLabel().isDefault(); + } + + + /** + * Returns a stream of all branches of this switch. + */ + default NodeStream getBranches() { + return children(ASTSwitchBranch.class); + } + + + /** + * Gets the expression tested by this switch. + * This is the expression between the parentheses. + */ + default ASTExpression getTestedExpression() { + return (ASTExpression) jjtGetChild(0); + } + + + /** + * Returns true if this switch statement tests an expression + * having an enum type and all the constants of this type + * are covered by a switch case. Returns false if the type of + * the tested expression could not be resolved. + */ + default boolean isExhaustiveEnumSwitch() { + ASTExpression expression = getTestedExpression(); + + if (expression.getType() == null) { + return false; + } + + if (Enum.class.isAssignableFrom(expression.getType())) { + + @SuppressWarnings("unchecked") + Set constantNames = EnumUtils.getEnumMap((Class) expression.getType()).keySet(); + + for (ASTSwitchBranch branch : this) { + // since this is an enum switch, the labels are necessarily + // the simple name of some enum constant. + + constantNames.remove(branch.getLabel().getFirstDescendantOfType(ASTName.class).getImage()); + + } + + return constantNames.isEmpty(); + } + + return false; + } + + + @Override + default Iterator iterator() { + return children(ASTSwitchBranch.class).iterator(); + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchStatement.java index 55440472d4..9d59e889ad 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchStatement.java @@ -4,18 +4,12 @@ package net.sourceforge.pmd.lang.java.ast; -import java.util.Iterator; -import java.util.Set; - -import org.apache.commons.lang3.EnumUtils; - - /** - * Represents a {@code switch} statement. Its syntax is identical to - * {@link ASTSwitchExpression SwitchExpression}, though it does not need - * to be exhaustive. See the doc of that node for details. + * Represents a {@code switch} statement. See {@link ASTSwitchLike} for + * its grammar. */ -public final class ASTSwitchStatement extends AbstractStatement implements Iterable { +public final class ASTSwitchStatement extends AbstractStatement implements ASTSwitchLike { + ASTSwitchStatement(int id) { super(id); } @@ -36,64 +30,4 @@ public final class ASTSwitchStatement extends AbstractStatement implements Itera visitor.visit(this, data); } - - /** - * Returns true if this switch has a {@code default} case. - */ - public boolean hasDefaultCase() { - for (ASTSwitchLabel label : this) { - if (label.isDefault()) { - return true; - } - } - return false; - } - - - /** - * Gets the expression tested by this switch. - * This is the expression between the parentheses. - */ - public ASTExpression getTestedExpression() { - return (ASTExpression) jjtGetChild(0); - } - - - /** - * Returns true if this switch statement tests an expression - * having an enum type and all the constants of this type - * are covered by a switch case. Returns false if the type of - * the tested expression could not be resolved. - */ - public boolean isExhaustiveEnumSwitch() { - ASTExpression expression = getTestedExpression(); - - if (expression.getType() == null) { - return false; - } - - if (Enum.class.isAssignableFrom(expression.getType())) { - - @SuppressWarnings("unchecked") - Set constantNames = EnumUtils.getEnumMap((Class) expression.getType()).keySet(); - - for (ASTSwitchLabel label : this) { - // since this is an enum switch, the labels are necessarily - // the simple name of some enum constant. - - constantNames.remove(label.getFirstDescendantOfType(ASTName.class).getImage()); - - } - - return constantNames.isEmpty(); - } - - return false; - } - - - @Override - public Iterator iterator() { - return children(ASTSwitchLabel.class).iterator(); - } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTThrowStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTThrowStatement.java index 5eb284c6cd..8043987d02 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTThrowStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTThrowStatement.java @@ -13,7 +13,7 @@ package net.sourceforge.pmd.lang.java.ast; * * */ -public final class ASTThrowStatement extends AbstractStatement { +public final class ASTThrowStatement extends AbstractStatement implements ASTSwitchArrowRHS { ASTThrowStatement(int id) { super(id); @@ -34,7 +34,9 @@ public final class ASTThrowStatement extends AbstractStatement { visitor.visit(this, data); } - /** Returns the expression for the thrown exception. */ + /** + * Returns the expression for the thrown exception. + */ public ASTExpression getExpr() { return (ASTExpression) getFirstChild(); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTYieldStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTYieldStatement.java index 7e1fb2c0cc..2799778702 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTYieldStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTYieldStatement.java @@ -23,7 +23,6 @@ public class ASTYieldStatement extends AbstractStatement { super(p, id); } - @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/SideEffectingVisitorAdapter.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/SideEffectingVisitorAdapter.java index 4498bc3d82..74946885b7 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/SideEffectingVisitorAdapter.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/SideEffectingVisitorAdapter.java @@ -37,10 +37,6 @@ public class SideEffectingVisitorAdapter implements SideEffectingVisitor { // TODO delegation - public void visit(ASTSwitchLabeledRule node, T data) { - visit((JavaNode) node, data); - } - public void visit(ASTAnyTypeDeclaration node, T data) { visit((JavaNode) node, data); } 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 0258fdd3d4..e44e497a65 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 @@ -31,9 +31,9 @@ import net.sourceforge.pmd.lang.java.ast.ASTNumericLiteral; import net.sourceforge.pmd.lang.java.ast.ASTReceiverParameter; import net.sourceforge.pmd.lang.java.ast.ASTResource; import net.sourceforge.pmd.lang.java.ast.ASTStringLiteral; +import net.sourceforge.pmd.lang.java.ast.ASTSwitchArrowBranch; import net.sourceforge.pmd.lang.java.ast.ASTSwitchExpression; import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel; -import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabeledRule; import net.sourceforge.pmd.lang.java.ast.ASTTryStatement; import net.sourceforge.pmd.lang.java.ast.ASTType; import net.sourceforge.pmd.lang.java.ast.ASTTypeArguments; @@ -275,7 +275,7 @@ public class LanguageLevelChecker { } @Override - public void visit(ASTSwitchLabeledRule node, T data) { + public void visit(ASTSwitchArrowBranch node, T data) { check(node, PreviewFeature.SWITCH_RULES, data); visitChildren(node, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/internal/visitors/CycloVisitor.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/internal/visitors/CycloVisitor.java index 2d7a882cc6..ae895b78cd 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/internal/visitors/CycloVisitor.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/internal/visitors/CycloVisitor.java @@ -14,7 +14,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTDoStatement; import net.sourceforge.pmd.lang.java.ast.ASTForStatement; import net.sourceforge.pmd.lang.java.ast.ASTForeachStatement; import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; -import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel; +import net.sourceforge.pmd.lang.java.ast.ASTSwitchBranch; import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement; import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement; import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement; @@ -59,16 +59,16 @@ public class CycloVisitor extends JavaParserVisitorAdapter { ((MutableInt) data).add(CycloMetric.booleanExpressionComplexity(node.getTestedExpression())); } - for (ASTSwitchLabel label : node) { - if (label.isDefault()) { + for (ASTSwitchBranch branch : node) { + if (branch.getLabel().isDefault()) { // like for "else", default is not a decision point continue; } if (considerBooleanPaths) { ((MutableInt) data).increment(); - } else if (node.jjtGetNumChildren() > 1 + label.jjtGetChildIndex() - && node.jjtGetChild(label.jjtGetChildIndex() + 1) instanceof ASTBlockStatement) { + } else if (node.jjtGetNumChildren() > 1 + branch.jjtGetChildIndex() + && node.jjtGetChild(branch.jjtGetChildIndex() + 1) instanceof ASTBlockStatement) { // an empty label is only counted if we count boolean paths ((MutableInt) data).increment(); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java index 075b65c6f8..b30dd2d5e3 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java @@ -34,9 +34,6 @@ import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; import net.sourceforge.pmd.lang.java.ast.ASTArguments; import net.sourceforge.pmd.lang.java.ast.ASTArrayDimsAndInits; import net.sourceforge.pmd.lang.java.ast.ASTArrayType; -import net.sourceforge.pmd.lang.java.ast.ASTBlock; -import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement; -import net.sourceforge.pmd.lang.java.ast.ASTBreakStatement; import net.sourceforge.pmd.lang.java.ast.ASTCastExpression; import net.sourceforge.pmd.lang.java.ast.ASTClassLiteral; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; @@ -71,7 +68,6 @@ import net.sourceforge.pmd.lang.java.ast.ASTReferenceType; import net.sourceforge.pmd.lang.java.ast.ASTRelationalExpression; import net.sourceforge.pmd.lang.java.ast.ASTShiftExpression; import net.sourceforge.pmd.lang.java.ast.ASTSwitchExpression; -import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabeledRule; import net.sourceforge.pmd.lang.java.ast.ASTType; import net.sourceforge.pmd.lang.java.ast.ASTTypeArgument; import net.sourceforge.pmd.lang.java.ast.ASTTypeArguments; @@ -83,7 +79,6 @@ import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer; import net.sourceforge.pmd.lang.java.ast.ASTWildcardBounds; -import net.sourceforge.pmd.lang.java.ast.ASTYieldStatement; import net.sourceforge.pmd.lang.java.ast.InternalApiBridge; import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorAdapter; import net.sourceforge.pmd.lang.java.ast.TypeNode; @@ -1135,60 +1130,59 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter { @Override public Object visit(ASTSwitchExpression node, Object data) { super.visit(node, data); - - JavaTypeDefinition type = null; - // first try to determine the type based on the first expression/break/yield of a switch rule - List rules = node.findChildrenOfType(ASTSwitchLabeledRule.class); - for (ASTSwitchLabeledRule rule : rules) { - Node body = rule.jjtGetChild(1); // second child is either Expression, Block, ThrowStatement - if (body instanceof ASTExpression) { - type = ((ASTExpression) body).getTypeDefinition(); - break; - } else if (body instanceof ASTBlock) { - List breaks = body.findDescendantsOfType(ASTBreakStatement.class); - if (!breaks.isEmpty()) { - ASTExpression expression = breaks.get(0).getFirstChildOfType(ASTExpression.class); - if (expression != null) { - type = expression.getTypeDefinition(); - break; - } - } - List yields = body.findDescendantsOfType(ASTYieldStatement.class); - if (!yields.isEmpty()) { - ASTExpression expression = yields.get(0).getFirstChildOfType(ASTExpression.class); - if (expression != null) { - type = expression.getTypeDefinition(); - break; - } - } - } - } - if (type == null) { - // now check the labels and their expressions of break/yield statements - for (int i = 0; i < node.jjtGetNumChildren(); i++) { - Node child = node.jjtGetChild(i); - if (child instanceof ASTBlockStatement) { - List breaks = child.findDescendantsOfType(ASTBreakStatement.class); - if (!breaks.isEmpty()) { - ASTExpression expression = breaks.get(0).getFirstChildOfType(ASTExpression.class); - if (expression != null) { - type = expression.getTypeDefinition(); - break; - } - } - List yields = child.findDescendantsOfType(ASTYieldStatement.class); - if (!yields.isEmpty()) { - ASTExpression expression = yields.get(0).getFirstChildOfType(ASTExpression.class); - if (expression != null && expression.getTypeDefinition() != null) { - type = expression.getTypeDefinition(); - break; - } - } - } - } - } - - setTypeDefinition(node, type); + // + // JavaTypeDefinition type = null; + // // first try to determine the type based on the first expression/break/yield of a switch rule + // List rules = node.findChildrenOfType(ASTSwitchLabeledRule.class); + // for (ASTSwitchLabeledRule rule : rules) { + // Node body = rule.jjtGetChild(1); // second child is either Expression, Block, ThrowStatement + // if (body instanceof ASTExpression) { + // type = ((ASTExpression) body).getTypeDefinition(); + // break; + // } else if (body instanceof ASTBlock) { + // List breaks = body.findDescendantsOfType(ASTBreakStatement.class); + // if (!breaks.isEmpty()) { + // ASTExpression expression = breaks.get(0).getFirstChildOfType(ASTExpression.class); + // if (expression != null) { + // type = expression.getTypeDefinition(); + // break; + // } + // } + // List yields = body.findDescendantsOfType(ASTYieldStatement.class); + // if (!yields.isEmpty()) { + // ASTExpression expression = yields.get(0).getFirstChildOfType(ASTExpression.class); + // if (expression != null) { + // type = expression.getTypeDefinition(); + // break; + // } + // } + // } + // } + // if (type == null) { + // // now check the labels and their expressions of break/yield statements + // for (int i = 0; i < node.jjtGetNumChildren(); i++) { + // Node child = node.jjtGetChild(i); + // if (child instanceof ASTBlockStatement) { + // List breaks = child.findDescendantsOfType(ASTBreakStatement.class); + // if (!breaks.isEmpty()) { + // ASTExpression expression = breaks.get(0).getFirstChildOfType(ASTExpression.class); + // if (expression != null) { + // type = expression.getTypeDefinition(); + // break; + // } + // } + // List yields = child.findDescendantsOfType(ASTYieldStatement.class); + // if (!yields.isEmpty()) { + // ASTExpression expression = yields.get(0).getFirstChildOfType(ASTExpression.class); + // if (expression != null && expression.getTypeDefinition() != null) { + // type = expression.getTypeDefinition(); + // break; + // } + // } + // } + // } + // } + // setTypeDefinition(node, type); return data; } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java12Test.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java12Test.java index 224d23fdbf..5113c8f3a6 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java12Test.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java12Test.java @@ -8,7 +8,6 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import org.apache.commons.io.IOUtils; -import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; @@ -31,88 +30,15 @@ public class Java12Test { ParserTstUtil.parseAndTypeResolveJava("11", loadSource("MultipleCaseLabels.java")); } - @Test - public void testMultipleCaseLabels() { - ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("12-preview", - loadSource("MultipleCaseLabels.java")); - Assert.assertNotNull(compilationUnit); - ASTSwitchStatement switchStatement = compilationUnit.getFirstDescendantOfType(ASTSwitchStatement.class); - Assert.assertTrue(switchStatement.jjtGetChild(0) instanceof ASTExpression); - Assert.assertTrue(switchStatement.jjtGetChild(1) instanceof ASTSwitchLabel); - ASTSwitchLabel switchLabel = switchStatement.getFirstChildOfType(ASTSwitchLabel.class); - Assert.assertEquals(3, switchLabel.findChildrenOfType(ASTExpression.class).size()); - } - @Test(expected = ParseException.class) public void testSwitchRulesJava11() { ParserTstUtil.parseAndTypeResolveJava("11", loadSource("SwitchRules.java")); } - @Test - public void testSwitchRules() { - ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("12-preview", - loadSource("SwitchRules.java")); - Assert.assertNotNull(compilationUnit); - ASTSwitchStatement switchStatement = compilationUnit.getFirstDescendantOfType(ASTSwitchStatement.class); - Assert.assertTrue(switchStatement.jjtGetChild(0) instanceof ASTExpression); - Assert.assertTrue(switchStatement.jjtGetChild(1) instanceof ASTSwitchLabeledExpression); - ASTSwitchLabeledExpression switchLabeledExpression = (ASTSwitchLabeledExpression) switchStatement.jjtGetChild(1); - Assert.assertEquals(2, switchLabeledExpression.jjtGetNumChildren()); - Assert.assertTrue(switchLabeledExpression.jjtGetChild(0) instanceof ASTSwitchLabel); - Assert.assertTrue(switchLabeledExpression.jjtGetChild(1) instanceof ASTExpression); - - ASTSwitchLabeledBlock switchLabeledBlock = (ASTSwitchLabeledBlock) switchStatement.jjtGetChild(4); - Assert.assertEquals(2, switchLabeledBlock.jjtGetNumChildren()); - Assert.assertTrue(switchLabeledBlock.jjtGetChild(0) instanceof ASTSwitchLabel); - Assert.assertTrue(switchLabeledBlock.jjtGetChild(1) instanceof ASTBlock); - - ASTSwitchLabeledThrowStatement switchLabeledThrowStatement = (ASTSwitchLabeledThrowStatement) switchStatement.jjtGetChild(5); - Assert.assertEquals(2, switchLabeledThrowStatement.jjtGetNumChildren()); - Assert.assertTrue(switchLabeledThrowStatement.jjtGetChild(0) instanceof ASTSwitchLabel); - Assert.assertTrue(switchLabeledThrowStatement.jjtGetChild(1) instanceof ASTThrowStatement); - } @Test(expected = ParseException.class) public void testSwitchExpressionsJava11() { ParserTstUtil.parseAndTypeResolveJava("11", loadSource("SwitchExpressions.java")); } - @Test - public void testSwitchExpressions() { - ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("12-preview", - loadSource("SwitchExpressions.java")); - Assert.assertNotNull(compilationUnit); - - ASTSwitchExpression switchExpression = compilationUnit.getFirstDescendantOfType(ASTSwitchExpression.class); - Assert.assertEquals(6, switchExpression.jjtGetNumChildren()); - Assert.assertTrue(switchExpression.jjtGetChild(0) instanceof ASTExpression); - Assert.assertEquals(5, switchExpression.findChildrenOfType(ASTSwitchLabeledRule.class).size()); - - ASTLocalVariableDeclaration localVar = compilationUnit.findDescendantsOfType(ASTLocalVariableDeclaration.class).get(1); - ASTVariableDeclarator localVarDecl = localVar.getFirstChildOfType(ASTVariableDeclarator.class); - Assert.assertEquals(Integer.TYPE, localVarDecl.getType()); - Assert.assertEquals(Integer.TYPE, switchExpression.getType()); - } - - @Test - public void testSwitchExpressionsBreak() { - ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("12-preview", - loadSource("SwitchExpressionsBreak.java")); - Assert.assertNotNull(compilationUnit); - - ASTSwitchExpression switchExpression = compilationUnit.getFirstDescendantOfType(ASTSwitchExpression.class); - Assert.assertEquals(11, switchExpression.jjtGetNumChildren()); - Assert.assertTrue(switchExpression.jjtGetChild(0) instanceof ASTExpression); - Assert.assertEquals(5, switchExpression.findChildrenOfType(ASTSwitchLabel.class).size()); - - ASTBreakStatement breakStatement = switchExpression.getFirstDescendantOfType(ASTBreakStatement.class); - Assert.assertEquals("SwitchExpressionsBreak.SIX", breakStatement.getImage()); - Assert.assertTrue(breakStatement.jjtGetChild(0) instanceof ASTExpression); - - ASTLocalVariableDeclaration localVar = compilationUnit.findDescendantsOfType(ASTLocalVariableDeclaration.class).get(1); - ASTVariableDeclarator localVarDecl = localVar.getFirstChildOfType(ASTVariableDeclarator.class); - Assert.assertEquals(Integer.TYPE, localVarDecl.getType()); - Assert.assertEquals(Integer.TYPE, switchExpression.getType()); - } - } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java13Test.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java13Test.java index 359828f482..3198bcf035 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java13Test.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java13Test.java @@ -8,7 +8,6 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import org.apache.commons.io.IOUtils; -import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; @@ -27,44 +26,6 @@ public class Java13Test { } } - @Test - public void testSwitchExpressions() { - ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("13-preview", - loadSource("SwitchExpressions.java")); - Assert.assertNotNull(compilationUnit); - - ASTSwitchExpression switchExpression = compilationUnit.getFirstDescendantOfType(ASTSwitchExpression.class); - Assert.assertEquals(4, switchExpression.jjtGetNumChildren()); - Assert.assertTrue(switchExpression.jjtGetChild(0) instanceof ASTExpression); - Assert.assertEquals(3, switchExpression.findChildrenOfType(ASTSwitchLabeledRule.class).size()); - Assert.assertEquals(1, switchExpression.findChildrenOfType(ASTSwitchLabeledBlock.class).size()); - Assert.assertEquals(1, switchExpression.findDescendantsOfType(ASTYieldStatement.class).size()); - ASTYieldStatement yieldStatement = switchExpression.getFirstDescendantOfType(ASTYieldStatement.class); - Assert.assertEquals(Integer.TYPE, yieldStatement.getExpr().getType()); - } - - @Test - public void testSwitchExpressionsYield() { - ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("13-preview", - loadSource("SwitchExpressionsYield.java")); - Assert.assertNotNull(compilationUnit); - - ASTSwitchExpression switchExpression = compilationUnit.getFirstDescendantOfType(ASTSwitchExpression.class); - Assert.assertEquals(11, switchExpression.jjtGetNumChildren()); - Assert.assertTrue(switchExpression.jjtGetChild(0) instanceof ASTExpression); - Assert.assertEquals(5, switchExpression.findChildrenOfType(ASTSwitchLabel.class).size()); - - ASTYieldStatement yieldStatement = switchExpression.getFirstDescendantOfType(ASTYieldStatement.class); - Assert.assertEquals("SwitchExpressionsBreak.SIX", yieldStatement.getImage()); - Assert.assertTrue(yieldStatement.jjtGetChild(0) instanceof ASTExpression); - - ASTLocalVariableDeclaration localVar = compilationUnit.findDescendantsOfType(ASTLocalVariableDeclaration.class) - .get(1); - ASTVariableDeclarator localVarDecl = localVar.getFirstChildOfType(ASTVariableDeclarator.class); - Assert.assertEquals(Integer.TYPE, localVarDecl.getType()); - Assert.assertEquals(Integer.TYPE, switchExpression.getType()); - } - @Test(expected = ParseException.class) public void testSwitchExpressionsBeforeJava13() { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java index 5d0fa458aa..ab9828a04d 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java @@ -286,7 +286,7 @@ public class ParserCornersTest { ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("11", readAsString("SwitchWithFallthrough.java")); Assert.assertNotNull(compilationUnit); ASTSwitchStatement switchStatement = compilationUnit.getFirstDescendantOfType(ASTSwitchStatement.class); - Assert.assertEquals(2, switchStatement.findChildrenOfType(ASTSwitchLabel.class).size()); + Assert.assertEquals(2, switchStatement.findChildrenOfType(ASTSwitchFallthroughBranch.class).size()); } @Test @@ -294,7 +294,7 @@ public class ParserCornersTest { ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("11", readAsString("SwitchStatements.java")); Assert.assertNotNull(compilationUnit); ASTSwitchStatement switchStatement = compilationUnit.getFirstDescendantOfType(ASTSwitchStatement.class); - Assert.assertEquals(2, switchStatement.findChildrenOfType(ASTSwitchLabel.class).size()); + Assert.assertEquals(2, switchStatement.findChildrenOfType(ASTSwitchBranch.class).size()); } diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTSwitchExpressionTests.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTSwitchExpressionTests.kt index 1becc95870..7b13266b01 100644 --- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTSwitchExpressionTests.kt +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTSwitchExpressionTests.kt @@ -6,7 +6,12 @@ package net.sourceforge.pmd.lang.java.ast import net.sourceforge.pmd.lang.ast.test.shouldBe import net.sourceforge.pmd.lang.java.ast.BinaryOp.* +import net.sourceforge.pmd.lang.java.ast.JavaVersion.Companion.Earliest +import net.sourceforge.pmd.lang.java.ast.JavaVersion.Companion.Latest +import net.sourceforge.pmd.lang.java.ast.JavaVersion.J12__PREVIEW +import net.sourceforge.pmd.lang.java.ast.JavaVersion.J13__PREVIEW import net.sourceforge.pmd.lang.java.ast.ParserTestCtx.Companion.ExpressionParsingCtx +import net.sourceforge.pmd.lang.java.ast.ParserTestCtx.Companion.StatementParsingCtx import net.sourceforge.pmd.lang.java.ast.UnaryOp.UNARY_MINUS @@ -16,7 +21,8 @@ import net.sourceforge.pmd.lang.java.ast.UnaryOp.UNARY_MINUS class ASTSwitchExpressionTests : ParserTestSpec({ - parserTest("Simple switch expressions") { + parserTest("No switch expr before j12 preview", javaVersions = !J12__PREVIEW) { + inContext(ExpressionParsingCtx) { """ @@ -27,94 +33,125 @@ class ASTSwitchExpressionTests : ParserTestSpec({ default -> { int k = day * 2; int result = f(k); - break result; + break result * 4; } } - """.trimIndent() should matchExpr { - it::getTestedExpression shouldBe variableAccess("day") + """ shouldNot parse() + } - child { - child { - it::isDefault shouldBe false + } - variableAccess("FRIDAY") - variableAccess("SUNDAY") + parserTest("No yield stmt before j13 preview", javaVersions = !J13__PREVIEW) { + inContext(ExpressionParsingCtx) { + + """ + switch (day) { + default -> { + yield result * 4; } - int(6) } + """ shouldNot parse() + } - child { - child { - it::isDefault shouldBe false + } - variableAccess("WEDNESDAY") + + parserTest("Simple switch expressions", javaVersions = listOf(J13__PREVIEW)) { + + + inContext(ExpressionParsingCtx) { + + + """ + switch (day) { + case FRIDAY, SUNDAY -> 6; + case WEDNESDAY -> 9; + case SONNABEND -> throw new MindBlownException(); + default -> { + int k = day * 2; + int result = f(k); + yield result; } - int(9) } + """ should parseAs { + switchExpr { + it::getTestedExpression shouldBe variableAccess("day") - child { - child { - it::isDefault shouldBe false - - variableAccess("SONNABEND") - } - child { - child { - child { - it::getTypeImage shouldBe "MindBlownException" + switchArrow { + it::getLabel shouldBe switchLabel { + variableAccess("FRIDAY") + variableAccess("SUNDAY") } - child {} + int(6) } - } - } - child { - child { - it::isDefault shouldBe true - } - block { - localVarDecl() - localVarDecl() - breakStatement(label = "result") + + switchArrow { + it::getLabel shouldBe switchLabel { + variableAccess("WEDNESDAY") + } + int(9) + } + + + switchArrow { + it::getLabel shouldBe switchLabel { + variableAccess("SONNABEND") + } + throwStatement() + } + + switchArrow { + it::getLabel shouldBe switchDefaultLabel() + block { + localVarDecl() + localVarDecl() + yieldStatement { + variableAccess("result") + } + } + } } } } } - parserTest("Non-trivial labels") { + + parserTest("Non-trivial labels", javaVersions = listOf(J12__PREVIEW, J13__PREVIEW)) { + inContext(ExpressionParsingCtx) { - """ switch (day) { + """ + switch (day) { case a + b, 4 * 2 / Math.PI -> 6; } - """ should matchExpr { + """ should parseAs { + switchExpr { + it::getTestedExpression shouldBe variableAccess("day") - it::getTestedExpression shouldBe variableAccess("day") - - child { - - child { - it::isDefault shouldBe false - - additiveExpr(ADD) { - variableAccess("a") - variableAccess("b") - } - multiplicativeExpr(DIV) { - multiplicativeExpr(MUL) { - int(4) - int(2) + switchArrow { + it::getLabel shouldBe switchLabel { + infixExpr(ADD) { + variableAccess("a") + variableAccess("b") + } + infixExpr(DIV) { + infixExpr(MUL) { + int(4) + int(2) + } + fieldAccess("PI") + } } - fieldAccess("PI") + int(6) } } - int(6) } } } - parserTest("Switch precedence") { + parserTest("Switch expr precedence", javaVersions = listOf(J12__PREVIEW, J13__PREVIEW)) { inContext(ExpressionParsingCtx) { @@ -148,10 +185,11 @@ class ASTSwitchExpressionTests : ParserTestSpec({ } - parserTest("Nested switch expressions") { + parserTest("Nested switch expressions", javaVersions = listOf(J12__PREVIEW, J13__PREVIEW)) { + inContext(ExpressionParsingCtx) { - """ + """ switch (day) { case FRIDAY -> 6; case WEDNESDAY -> switch (foo) { @@ -160,62 +198,54 @@ class ASTSwitchExpressionTests : ParserTestSpec({ }; default -> 3; } - """.trimIndent() should matchExpr { + """.trimIndent() should parseAs { - it::getTestedExpression shouldBe variableAccess("day") + switchExpr { + it::getTestedExpression shouldBe variableAccess("day") - child { - - child { - it::isDefault shouldBe false - - variableAccess("FRIDAY") - } - int(6) - } - - child { - - child { - it::isDefault shouldBe false - - variableAccess("WEDNESDAY") - } - - child { - - it::getTestedExpression shouldBe variableAccess("foo") - - child { - child { - it::isDefault shouldBe false - - int(2) + switchArrow { + switchLabel { + variableAccess("FRIDAY") } - int(5) + int(6) } - child { - child { - it::isDefault shouldBe true + + switchArrow { + switchLabel { + variableAccess("WEDNESDAY") } + + switchExpr { + it::getTestedExpression shouldBe variableAccess("foo") + + switchArrow { + switchLabel { + int(2) + } + int(5) + } + switchArrow { + switchDefaultLabel() + int(3) + } + } + } + + switchArrow { + switchDefaultLabel() int(3) } } } - child { - child { - it::isDefault shouldBe true - } - int(3) - } } } - parserTest("Non-fallthrough nested in fallthrough") { + parserTest("Non-fallthrough nested in fallthrough", javaVersions = listOf(J12__PREVIEW, J13__PREVIEW)) { + inContext(StatementParsingCtx) { - """ + """ switch (day) { case FRIDAY: foo(); break; case WEDNESDAY : switch (foo) { @@ -224,120 +254,116 @@ class ASTSwitchExpressionTests : ParserTestSpec({ } default : bar(); } - """.trimIndent() should matchStmt { - it::isExhaustiveEnumSwitch shouldBe false + """ should parseAs { + switchStmt { - it::getTestedExpression shouldBe variableAccess("day") + it::isExhaustiveEnumSwitch shouldBe false + it::getTestedExpression shouldBe variableAccess("day") - child { - it::isDefault shouldBe false - - child(ignoreChildren = true) {} + switchFallthrough { + switchLabel { + variableAccess("FRIDAY") + } + exprStatement() + breakStatement() + } + switchFallthrough { + switchLabel { + variableAccess("WEDNESDAY") + } + switchStmt { + variableAccess("foo") + switchArrow { + switchLabel() + int(5) + } + switchArrow { + switchDefaultLabel() + int(3) + } + } + } + switchFallthrough { + switchDefaultLabel() + exprStatement() + } + } } - - exprStatement() - breakStatement() - child { - it::isDefault shouldBe false - - child(ignoreChildren = true) {} - - } - - child { - - it::getTestedExpression shouldBe child(ignoreChildren = true) {} - - child(ignoreChildren = true) {} - child(ignoreChildren = true) {} - - } - - - child { - it::isDefault shouldBe true - } - - exprStatement() } } - parserTest("Switch statement with non-fallthrough labels") { + parserTest("Switch statement with non-fallthrough labels", javaVersions = listOf(J12__PREVIEW, J13__PREVIEW)) { - """ + inContext(StatementParsingCtx) { + """ switch (day) { case THURSDAY, SATURDAY -> System.out.println(" 8"); case WEDNESDAY -> System.out.println(" 9"); } - """.trimIndent() should matchStmt { - it::isExhaustiveEnumSwitch shouldBe false + """ should parseAs { + switchStmt { - it::getTestedExpression shouldBe variableAccess("day") + it::getTestedExpression shouldBe variableAccess("day") - child { - child { - it::isDefault shouldBe false - variableAccess("THURSDAY") - variableAccess("SATURDAY") - } - child(ignoreChildren = true) { - it::getMethodName shouldBe "println" - } - } - child { - child { - it::isDefault shouldBe false - - variableAccess("WEDNESDAY") - } - child(ignoreChildren = true) { - it::getMethodName shouldBe "println" + switchArrow { + switchLabel { + variableAccess("THURSDAY") + variableAccess("SATURDAY") + } + methodCall("println") + } + switchArrow { + switchLabel { + variableAccess("WEDNESDAY") + } + methodCall("println") + } } } } } - parserTest("Fallthrough switch statement") { + parserTest("Fallthrough switch statement", javaVersions = Earliest..Latest) { - """ + inContext(StatementParsingCtx) { + """ switch (day) { case TUESDAY : System.out.println(" 7"); break; - case THURSDAY, SATURDAY : System.out.println(" 8"); break; + case THURSDAY : System.out.println(" 8"); break; default : break; } - """.trimIndent() should matchStmt { - it::isExhaustiveEnumSwitch shouldBe false + """ should parseAs { + switchStmt { - it::getTestedExpression shouldBe variableAccess("day") + it::getTestedExpression shouldBe variableAccess("day") + switchFallthrough { + switchLabel { + variableAccess("TUESDAY") + } - child { - it::isDefault shouldBe false + exprStatement() + breakStatement() + } - variableAccess("TUESDAY") + switchFallthrough { + switchLabel { + variableAccess("THURSDAY") + } + + exprStatement() + breakStatement() + } + + switchFallthrough { + switchDefaultLabel() + breakStatement() + } + } } - - exprStatement() - breakStatement() - - child { - it::isDefault shouldBe false - - child(ignoreChildren = true) {} - child(ignoreChildren = true) {} - } - - exprStatement() - breakStatement() - - child { - it::isDefault shouldBe true - } - - breakStatement() } } diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/TestExtensions.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/TestExtensions.kt index 5380f95778..44b050506c 100644 --- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/TestExtensions.kt +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/TestExtensions.kt @@ -235,6 +235,19 @@ fun TreeNodeWrapper.breakStatement(label: String? = null, contents: Nod contents() } +fun TreeNodeWrapper.yieldStatement(contents: ValuedNodeSpec = {null}) = + child { + val e = contents() + if (e != null) it::getExpr shouldBe e + else unspecifiedChild() + } + + +fun TreeNodeWrapper.throwStatement(contents: NodeSpec = EmptyAssertions) = + child(ignoreChildren = contents == EmptyAssertions) { + contents() + } + fun TreeNodeWrapper.localVarDecl(contents: NodeSpec = EmptyAssertions) = child(ignoreChildren = contents == EmptyAssertions) { contents() @@ -393,6 +406,34 @@ fun TreeNodeWrapper.switchExpr(assertions: NodeSpec.switchStmt(assertions: NodeSpec = EmptyAssertions) = + child(ignoreChildren = assertions == EmptyAssertions) { + assertions() + } + +fun TreeNodeWrapper.switchArrow(rhs: ValuedNodeSpec = { null }) = + child { + val rhs = rhs() + if (rhs != null) it::getRightHandSide shouldBe rhs + else unspecifiedChildren(2) // label + rhs + } + +fun TreeNodeWrapper.switchFallthrough(assertions: NodeSpec = EmptyAssertions) = + child(ignoreChildren = assertions == EmptyAssertions) { + assertions() + } +fun TreeNodeWrapper.switchLabel(assertions: NodeSpec = EmptyAssertions) = + child(ignoreChildren = assertions == EmptyAssertions) { + it::isDefault shouldBe false + assertions() + } + +fun TreeNodeWrapper.switchDefaultLabel(assertions: NodeSpec = EmptyAssertions) = + child(ignoreChildren = assertions == EmptyAssertions) { + it::isDefault shouldBe true + it::getExprList shouldBe emptyList() + assertions() + } fun TreeNodeWrapper.number(primitiveType: ASTPrimitiveType.PrimitiveType? = null, assertions: NodeSpec = EmptyAssertions) = child { diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java13/SwitchExpressions.java b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java13/SwitchExpressions.java deleted file mode 100644 index 4800068de4..0000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java13/SwitchExpressions.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * - * - * @see
JEP 354: Switch Expressions (Preview) - */ -public class SwitchExpressions { - private enum Day { - MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY; - } - - public static void main(String[] args) { - Day day = Day.FRIDAY; - - int j = switch (day) { - case MONDAY -> 0; - case TUESDAY -> 1; - default -> { - int k = day.toString().length(); - int result = f(k); - yield result; - } - }; - System.out.printf("j = %d%n", j); - - String s = "Bar"; - int result = switch (s) { - case "Foo": - yield 1; - case "Bar": - yield 2; - default: - System.out.println("Neither Foo nor Bar, hmmm..."); - yield 0; - }; - System.out.printf("result = %d%n", result); - } - - private static int f(int k) { - return k+1; - } -} \ No newline at end of file diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java13/SwitchExpressionsYield.java b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java13/SwitchExpressionsYield.java deleted file mode 100644 index fd8ca7bf70..0000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java13/SwitchExpressionsYield.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * - * @see JEP 325: Switch Expressions (Preview) - */ -public class SwitchExpressionsYield { - private static final int MONDAY = 1; - private static final int TUESDAY = 2; - private static final int WEDNESDAY = 3; - private static final int THURSDAY = 4; - private static final int FRIDAY = 5; - private static final int SATURDAY = 6; - private static final int SUNDAY = 7; - - private static final int SIX = 6; - - public static void main(String[] args) { - int day = FRIDAY; - - var numLetters = switch (day) { - case MONDAY, FRIDAY, SUNDAY: yield SwitchExpressionsBreak.SIX; - case TUESDAY : yield 7; - case THURSDAY, SATURDAY : yield 8; - case WEDNESDAY : yield 9; - default : { - int k = day * 2; - int result = f(k); - yield result; - } - }; - System.out.printf("NumLetters: %d%n", numLetters); - } - - private static int f(int k) { - return k*3; - } -}