Skip to content

Commit

Permalink
Issue checkstyle#6615: Add support for Java 14 switch and yield
Browse files Browse the repository at this point in the history
  • Loading branch information
nrmancuso committed Aug 10, 2020
1 parent 6455f8c commit 8f1b800
Show file tree
Hide file tree
Showing 10 changed files with 3,504 additions and 23 deletions.
3 changes: 1 addition & 2 deletions config/ant-phase-verify.xml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
<exclude name="**/InputJava14InstanceofWithPatternMatchingAST.txt"/>
<exclude name="**/InputJava14Records.txt"/>
<exclude name="**/InputJava14TextBlocks.txt"/>
<exclude name="**/InputJava14SwitchExpression.txt"/>

</fileset>
</path>
Expand Down Expand Up @@ -155,8 +156,6 @@
<exclude name="**/InputMainFrameModelIncorrectClass.java"/>
<exclude name="**/InputBeforeExecutionExclusionFileFilterIncorrectClass.java"/>
<exclude name="**/InputJavaParser.java"/>
<!-- until https://github.com/checkstyle/checkstyle/issues/6615 -->
<exclude name="**/InputJava14SwitchExpression.java"/>

</fileset>
</path>
Expand Down
130 changes: 130 additions & 0 deletions src/main/java/com/puppycrawl/tools/checkstyle/api/TokenTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -984,6 +984,7 @@ public final class TokenTypes {
* @see #LITERAL_CASE
* @see #LITERAL_DEFAULT
* @see #LITERAL_SWITCH
* @see #LITERAL_YIELD
**/
public static final int CASE_GROUP = GeneratedJavaTokenTypes.CASE_GROUP;
/**
Expand Down Expand Up @@ -1861,6 +1862,7 @@ public final class TokenTypes {
* @see #CASE_GROUP
* @see #RCURLY
* @see #SLIST
* @see #SWITCH_RULE
**/
public static final int LITERAL_SWITCH =
GeneratedJavaTokenTypes.LITERAL_switch;
Expand Down Expand Up @@ -1906,6 +1908,7 @@ public final class TokenTypes {
*
* @see #CASE_GROUP
* @see #MODIFIERS
* @see #SWITCH_RULE
**/
public static final int LITERAL_DEFAULT =
GeneratedJavaTokenTypes.LITERAL_default;
Expand Down Expand Up @@ -3815,6 +3818,133 @@ public final class TokenTypes {
public static final int TEXT_BLOCK_LITERAL_END =
GeneratedJavaTokenTypes.TEXT_BLOCK_LITERAL_END;

/**
* The {@code yield} keyword. This element appears
* as part of a yield statement.
*
* <p>For example:</p>
* <pre>
* int yield = 0; // not a keyword here
* return switch (mode) {
* case "a", "b":
* yield 1;
* default:
* yield - 1;
* };
* </pre>
* <p>parses as:</p>
* <pre>
* |--VARIABLE_DEF
* | |--MODIFIERS
* | |--TYPE
* | | `--LITERAL_INT (int)
* | |--IDENT (yield)
* | `--ASSIGN (=)
* | `--EXPR
* | `--NUM_INT (0)
* |--SEMI (;)
* |--LITERAL_RETURN (return)
* | |--EXPR
* | | `--LITERAL_SWITCH (switch)
* | | |--LPAREN (()
* | | |--EXPR
* | | | `--IDENT (mode)
* | | |--RPAREN ())
* | | |--LCURLY ({)
* | | |--CASE_GROUP
* | | | |--LITERAL_CASE (case)
* | | | | |--EXPR
* | | | | | `--STRING_LITERAL ("a")
* | | | | |--COMMA (,)
* | | | | |--EXPR
* | | | | | `--STRING_LITERAL ("b")
* | | | | `--COLON (:)
* | | | `--SLIST
* | | | `--LITERAL_YIELD (yield)
* | | | |--EXPR
* | | | | `--NUM_INT (1)
* | | | `--SEMI (;)
* | | |--CASE_GROUP
* | | | |--LITERAL_DEFAULT (default)
* | | | | `--COLON (:)
* | | | `--SLIST
* | | | `--LITERAL_YIELD (yield)
* | | | |--EXPR
* | | | | `--UNARY_MINUS (-)
* | | | | `--NUM_INT (1)
* | | | `--SEMI (;)
* | | `--RCURLY (})
* | `--SEMI (;)
* </pre>
*
*
* @see #LITERAL_SWITCH
* @see #CASE_GROUP
* @see #SLIST
* @see #SWITCH_RULE
*
* @see <a href="https://docs.oracle.com/javase/specs/jls/se13/preview/switch-expressions.html">
* Java Language Specification, &sect;14.21</a>
*
* @since 8.36
**/
public static final int LITERAL_YIELD =
GeneratedJavaTokenTypes.LITERAL_yield;

/**
* Switch Expressions.
*
* <p>For example:</p>
* <pre>
* return switch (day) {
* case SAT, SUN {@code ->} "Weekend";
* default {@code ->} "Working day";
* };
* </pre>
* <p>parses as:</p>
* <pre>
* LITERAL_RETURN (return)
* |--EXPR
* | `--LITERAL_SWITCH (switch)
* | |--LPAREN (()
* | |--EXPR
* | | `--IDENT (day)
* | |--RPAREN ())
* | |--LCURLY ({)
* | |--SWITCH_RULE
* | | |--LITERAL_CASE (case)
* | | | |--EXPR
* | | | | `--IDENT (SAT)
* | | | |--COMMA (,)
* | | | `--EXPR
* | | | `--IDENT (SUN)
* | | |--LAMBDA {@code ->}
* | | |--EXPR
* | | | `--STRING_LITERAL ("Weekend")
* | | `--SEMI (;)
* | |--SWITCH_RULE
* | | |--LITERAL_DEFAULT (default)
* | | |--LAMBDA {@code ->}
* | | |--EXPR
* | | | `--STRING_LITERAL ("Working day")
* | | `--SEMI (;)
* | `--RCURLY (})
* `--SEMI (;)
* </pre>
*
* @see #LITERAL_CASE
* @see #LITERAL_DEFAULT
* @see #LITERAL_SWITCH
* @see #LITERAL_YIELD
*
* @see <a href="https://docs.oracle.com/javase/specs/jls/se13/preview/switch-expressions.html">
* Java Language Specification, &sect;14.21</a>
*
* @since 8.36
**/
public static final int SWITCH_RULE =
GeneratedJavaTokenTypes.SWITCH_RULE;

/** Prevent instantiation. */
private TokenTypes() {
}
Expand Down
79 changes: 67 additions & 12 deletions src/main/resources/com/puppycrawl/tools/checkstyle/grammar/java.g
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ tokens {
PATTERN_VARIABLE_DEF; RECORD_DEF; LITERAL_record="record";
RECORD_COMPONENTS; RECORD_COMPONENT_DEF; COMPACT_CTOR_DEF;
TEXT_BLOCK_LITERAL_BEGIN; TEXT_BLOCK_CONTENT; TEXT_BLOCK_LITERAL_END;
LITERAL_yield="yield"; SWITCH_RULE;
}
{
Expand Down Expand Up @@ -209,6 +210,15 @@ tokens {
{
return ((currentLtLevel != 0) || ltCounter == currentLtLevel);
}
/**
* This int value tracks the depth of a switch expression. Along with the
* IDENT to id rule at the end of the parser, this value helps us
* to know if the "yield" we are parsing is an IDENT, method call, class,
* field, etc. or if it is a java 13+ yield statement. Positive values
* indicate that we are within a (possibly nested) switch expression.
*/
private int switchBlockDepth = 0;
}
// Compilation Unit: In Java, this is a single file. This is the start
Expand Down Expand Up @@ -1114,6 +1124,9 @@ traditionalStatement
// A list of statements in curly braces -- start a new scope!
: compoundStatement
// Yield statement, must be in a switchRule to use
| {this.switchBlockDepth>0}? yieldStatement
// declarations are ambiguous with "ID DOT" relative to expression
// statements. Must backtrack to be sure. Could use a semantic
// predicate to test symbol table to see what the type was coming
Expand All @@ -1123,6 +1136,9 @@ traditionalStatement
// we create an empty modifiers AST as we do for classes without modifiers
| recordDefinition[(AST) getASTFactory().create(MODIFIERS,"MODIFIERS")]

// switch/case statement
| switchExpression

// An expression statement. This could be a method call,
// assignment statement, or any other expression evaluated for
// side-effects.
Expand Down Expand Up @@ -1165,9 +1181,6 @@ traditionalStatement
// Return an expression
| "return"^ (expression)? SEMI

// switch/case statement
| switchExpression

// exception try-catch block
| tryBlock

Expand All @@ -1181,6 +1194,10 @@ traditionalStatement
| s:SEMI {#s.setType(EMPTY_STAT);}
;

yieldStatement!
: l:LITERAL_yield e:expression s:SEMI {## = #(l,e,s);}
;

forStatement
: f:"for"^
LPAREN
Expand Down Expand Up @@ -1213,6 +1230,16 @@ elseStatement
: "else"^ statement
;

switchBlock
: ({switchBlockDepth++;}: // inc counter since we are in a switch expression
LCURLY)
( ( ( switchRule )+)=>( ( switchRule )+ )
| ( ( casesGroup )*)=>( ( casesGroup )* )
)
({switchBlockDepth--;}: // dec counter since we are leaving a switch expression
RCURLY)
;

casesGroup
: ( // CONFLICT: to which case group do the statements bind?
// ANTLR generates proper code: it groups the
Expand All @@ -1222,16 +1249,12 @@ casesGroup
warnWhenFollowAmbig = false;
}
:
aCase
switchLabel
)+
(caseSList)?
{#casesGroup = #([CASE_GROUP, "CASE_GROUP"], #casesGroup);}
;

aCase
: ("case"^ expression | "default"^) COLON
;

caseSList
:
(
Expand All @@ -1247,6 +1270,37 @@ caseSList
{#caseSList = #(#[SLIST,"SLIST"],#caseSList);}
;

switchRule
: ( (switchLabeledExpression)=> se:switchLabeledExpression
| (switchLabeledBlock)=> sb:switchLabeledBlock
| (switchLabeledThrow)=> st:switchLabeledThrow
)
{## = #(#[SWITCH_RULE, "SWITCH_RULE"], se, sb, st);}
;

switchLabeledExpression
: switchLabel LAMBDA expression SEMI
;

switchLabeledBlock
: switchLabel LAMBDA compoundStatement
;

switchLabeledThrow
: switchLabel LAMBDA throwStatement
;

switchLabel
: ( LITERAL_case^ caseConstant (COMMA caseConstant)*
| LITERAL_default^
)
(COLON)?
;

caseConstant
: conditionalExpression {## = #(#[EXPR,"EXPR"],##);}
;

// The initializer for a for loop
forInit
// if it looks like a declaration, it is
Expand Down Expand Up @@ -1505,6 +1559,7 @@ unaryExpressionNotPlusMinus
: BNOT^ unaryExpression
| LNOT^ unaryExpression
| castExpression
| switchExpression
;

castExpression
Expand Down Expand Up @@ -1540,9 +1595,7 @@ castExpression
;
switchExpression
: "switch"^ LPAREN expression RPAREN LCURLY
( casesGroup )*
RCURLY
: "switch"^ LPAREN expression RPAREN switchBlock
;
typeCastParameters
Expand Down Expand Up @@ -1754,9 +1807,11 @@ textBlock
// This rule was created to remedy the "keyword as identifier" problem
// See: https://github.com/checkstyle/checkstyle/issues/8308
id: IDENT | recordKey ;
id: IDENT | recordKey | yieldKey;
recordKey: "record" {#recordKey.setType(IDENT);};
yieldKey: "yield" {#yieldKey.setType(IDENT);};
//----------------------------------------------------------------------------
// The Java scanner
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public void testOrderOfProperties() throws Exception {

@Test
public void testAcceptableTokensMakeSense() {
final int expectedTokenTypesTotalNumber = 178;
final int expectedTokenTypesTotalNumber = 180;
assertEquals(expectedTokenTypesTotalNumber, TokenUtil.getTokenTypesTotalNumber(),
"Total number of TokenTypes has changed, acceptable tokens in"
+ " IllegalTokenTextCheck need to be reconsidered.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,12 @@ public void testJava14TextBlocksEscapes() throws Exception {
getNonCompilablePath("java14/InputJava14TextBlocksEscapesAreOneChar.java"));
}

@Test
public void testJava14SwitchExpression() throws Exception {
verifyAst(getPath("java14/InputJava14SwitchExpression.txt"),
getNonCompilablePath("java14/InputJava14SwitchExpression.java"));
}

@Test
public void testImpossibleExceptions() throws Exception {
AssertGeneratedJavaLexer.verifyFail("mSTD_ESC", 'a');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,11 @@ public void testTokenNumbering() {
assertEquals(204, GeneratedJavaTokenTypes.TEXT_BLOCK_LITERAL_BEGIN, message);
assertEquals(205, GeneratedJavaTokenTypes.TEXT_BLOCK_CONTENT, message);
assertEquals(206, GeneratedJavaTokenTypes.TEXT_BLOCK_LITERAL_END, message);
assertEquals(207, GeneratedJavaTokenTypes.LITERAL_yield, message);
assertEquals(208, GeneratedJavaTokenTypes.SWITCH_RULE, message);

// Read JavaDoc before changing
assertEquals(205, GeneratedJavaTokenTypes.class.getDeclaredFields().length,
assertEquals(207, GeneratedJavaTokenTypes.class.getDeclaredFields().length,
"all tokens must be added to list in"
+ " 'GeneratedJavaTokenTypesTest' and verified"
+ " that their old numbering didn't change");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,9 @@ public class AllChecksTest extends AbstractModuleTestSupport {
"STRING_LITERAL", "ARRAY_DECLARATOR", "LITERAL_CASE",
"PATTERN_VARIABLE_DEF", "RECORD_DEF", "LITERAL_RECORD",
"RECORD_COMPONENTS", "RECORD_COMPONENT_DEF", "COMPACT_CTOR_DEF",
"TEXT_BLOCK_LITERAL_BEGIN", "TEXT_BLOCK_CONTENT",
"TEXT_BLOCK_LITERAL_END").collect(
Collectors.toSet()));
"TEXT_BLOCK_LITERAL_BEGIN", "TEXT_BLOCK_CONTENT", "TEXT_BLOCK_LITERAL_END",
"LITERAL_YIELD", "SWITCH_RULE")
.collect(Collectors.toSet()));
// we have no need to block specific token text
CHECKSTYLE_TOKENS_IN_CONFIG_TO_IGNORE.put("IllegalTokenText",
Stream.of("NUM_DOUBLE", "NUM_FLOAT", "NUM_INT", "NUM_LONG", "IDENT",
Expand Down
Loading

0 comments on commit 8f1b800

Please sign in to comment.