Skip to content

Commit

Permalink
Issue checkstyle#14195: Support Java 21 String Template Syntax (Text …
Browse files Browse the repository at this point in the history
…Blocks)
  • Loading branch information
nrmancuso committed Mar 4, 2024
1 parent 5d5f5b2 commit d875e8f
Show file tree
Hide file tree
Showing 16 changed files with 918 additions and 29 deletions.
8 changes: 0 additions & 8 deletions config/projects-to-test/openjdk21-excluded.files
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,6 @@
<property name="fileNamePattern" value="[\\/]test[\\/]langtools[\\/]tools[\\/]javac[\\/]unicode[\\/]FirstChar2.java$"/>
</module>

<!-- until https://github.com/checkstyle/checkstyle/issues/14195 (text block templates) -->
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="[\\/]test[\\/]jdk[\\/]java[\\/]lang[\\/]template[\\/]StringTemplateTest.java$"/>
</module>
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="[\\/]test[\\/]jdk[\\/]java[\\/]lang[\\/]template[\\/]Basic.java$"/>
</module>

<!-- until https://github.com/checkstyle/checkstyle/issues/13987 -->
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="[\\/]test[\\/]langtools[\\/]tools[\\/]javac[\\/]diags[\\/]examples[\\/]UnnamedClass.java$"/>
Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1136,7 +1136,7 @@
<limit>
<counter>BRANCH</counter>
<value>COVEREDRATIO</value>
<minimum>0.71</minimum>
<minimum>0.70</minimum>
</limit>
</limits>
</rule>
Expand All @@ -1154,7 +1154,7 @@
<limit>
<counter>BRANCH</counter>
<value>COVEREDRATIO</value>
<minimum>0.75</minimum>
<minimum>0.73</minimum>
</limit>
</limits>
</rule>
Expand Down
168 changes: 168 additions & 0 deletions src/main/java/com/puppycrawl/tools/checkstyle/JavaAstVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ public final class JavaAstVisitor extends JavaLanguageParserBaseVisitor<DetailAs
/** String representation of the double quote character. */
private static final String QUOTE = "\"";

/** String representation of the triple quote character. */
private static final String TRIPLE_QUOTE = "\"\"\"";

/** String representation of the string template embedded expression starting delimiter. */
private static final String EMBEDDED_EXPRESSION_BEGIN = "\\{";

Expand Down Expand Up @@ -1830,6 +1833,171 @@ public DetailAstImpl visitStringTemplate(JavaLanguageParser.StringTemplateContex
return begin;
}

@Override
public DetailAstImpl visitTextBlockTemplate(JavaLanguageParser.TextBlockTemplateContext ctx) {
final DetailAstImpl begin = buildTextBlockTemplateBeginning(ctx);

final Optional<DetailAstImpl> startExpression = Optional.ofNullable(ctx.expr())
.map(this::visit);

if (startExpression.isPresent()) {
final DetailAstImpl imaginaryExpr =
createImaginary(TokenTypes.EMBEDDED_EXPRESSION);
imaginaryExpr.addChild(startExpression.orElseThrow());
begin.addChild(imaginaryExpr);
}

ctx.textBlockTemplateMiddle().stream()
.map(this::buildTextBlockTemplateMiddle)
.collect(Collectors.toUnmodifiableList())
.forEach(begin::addChild);

final DetailAstImpl end = buildTextBlockTemplateEnd(ctx);
begin.addChild(end);
return begin;
}

/**
* Builds the end of a text block template AST.
*
* @param ctx the TextBlockTemplateContext to build AST from
* @return text block template AST
*/
private static DetailAstImpl buildTextBlockTemplateEnd(
JavaLanguageParser.TextBlockTemplateContext ctx) {

// token looks like '}' StringFragment '"""'
final TerminalNode context = ctx.TEXT_BLOCK_TEMPLATE_END();
final Token token = context.getSymbol();
final String tokenText = context.getText();
final int tokenStartIndex = token.getCharPositionInLine();
final int tokenLineNumber = token.getLine();
final int tokenTextLength = tokenText.length();

final DetailAstImpl embeddedExpressionEnd = createImaginary(
TokenTypes.EMBEDDED_EXPRESSION_END, EMBEDDED_EXPRESSION_END,
tokenLineNumber, tokenStartIndex
);

// remove delimiters '}' and '"'
final String stringFragment = tokenText.substring(
EMBEDDED_EXPRESSION_END.length(),
tokenTextLength - TRIPLE_QUOTE.length()
);

final DetailAstImpl endContent = createImaginary(
TokenTypes.TEXT_BLOCK_TEMPLATE_CONTENT, stringFragment,
tokenLineNumber,
tokenStartIndex + EMBEDDED_EXPRESSION_END.length()
);
embeddedExpressionEnd.addNextSibling(endContent);

final DetailAstImpl stringTemplateEnd = createImaginary(
TokenTypes.STRING_TEMPLATE_END, TRIPLE_QUOTE,
tokenLineNumber,
tokenStartIndex + tokenTextLength - TRIPLE_QUOTE.length()
);
endContent.addNextSibling(stringTemplateEnd);
return embeddedExpressionEnd;
}

/**
* Builds the middle of a text block template AST.
*
* @param middleContext the TextBlockTemplateMiddleContext to build AST from
* @return text block template middle AST
*/
private DetailAstImpl buildTextBlockTemplateMiddle(
JavaLanguageParser.TextBlockTemplateMiddleContext middleContext) {

// token looks like '}' TextBlockFragment '\{'
final Token token = middleContext.middle;
final int tokenStartIndex = token.getCharPositionInLine();
final int tokenLineNumber = token.getLine();
final String tokenText = token.getText();
final int tokenTextLength = tokenText.length();

final DetailAstImpl embeddedExpressionEnd = createImaginary(
TokenTypes.EMBEDDED_EXPRESSION_END, EMBEDDED_EXPRESSION_END,
tokenLineNumber, tokenStartIndex
);

// remove delimiters '}' and '\\' '{'
final String stringFragment = tokenText.substring(
EMBEDDED_EXPRESSION_END.length(),
tokenTextLength - EMBEDDED_EXPRESSION_BEGIN.length()
);

final DetailAstImpl content = createImaginary(
TokenTypes.TEXT_BLOCK_TEMPLATE_CONTENT, stringFragment,
tokenLineNumber, tokenStartIndex + EMBEDDED_EXPRESSION_END.length()
);
embeddedExpressionEnd.addNextSibling(content);

final DetailAstImpl embeddedBegin = createImaginary(
TokenTypes.EMBEDDED_EXPRESSION_BEGIN,
EMBEDDED_EXPRESSION_BEGIN,
tokenLineNumber,
tokenStartIndex + tokenTextLength - EMBEDDED_EXPRESSION_BEGIN.length()
);
content.addNextSibling(embeddedBegin);

final Optional<DetailAstImpl> embeddedExpression =
Optional.ofNullable(middleContext.getChild(1))
.map(this::visit);

if (embeddedExpression.isPresent()) {
final DetailAstImpl imaginaryExpr =
createImaginary(TokenTypes.EMBEDDED_EXPRESSION);
imaginaryExpr.addChild(embeddedExpression.orElseThrow());
embeddedExpressionEnd.addNextSibling(imaginaryExpr);
}

return embeddedExpressionEnd;
}

/**
* Builds the beginning of a text block template AST.
*
* @param ctx the TextBlockTemplateContext to build AST from
* @return text block template AST
*/
private static DetailAstImpl buildTextBlockTemplateBeginning(
JavaLanguageParser.TextBlockTemplateContext ctx) {

// token looks like '"' StringFragment '\{'
final TerminalNode context = ctx.TEXT_BLOCK_TEMPLATE_BEGIN();
final Token token = context.getSymbol();
final String tokenText = context.getText();
final int tokenStartIndex = token.getCharPositionInLine();
final int tokenLineNumber = token.getLine();
final int tokenTextLength = tokenText.length();

final DetailAstImpl textBlockTemplateBegin = createImaginary(
TokenTypes.TEXT_BLOCK_TEMPLATE_BEGIN, TRIPLE_QUOTE,
tokenLineNumber, tokenStartIndex
);

// remove delimiters '"' and '\{'
final String stringFragment = tokenText.substring(
TRIPLE_QUOTE.length(), tokenTextLength - EMBEDDED_EXPRESSION_BEGIN.length());

final DetailAstImpl stringTemplateContent = createImaginary(
TokenTypes.TEXT_BLOCK_TEMPLATE_CONTENT, stringFragment,
tokenLineNumber, tokenStartIndex + TRIPLE_QUOTE.length()
);
textBlockTemplateBegin.addChild(stringTemplateContent);

final DetailAstImpl embeddedBegin = createImaginary(
TokenTypes.EMBEDDED_EXPRESSION_BEGIN,
EMBEDDED_EXPRESSION_BEGIN,
tokenLineNumber,
tokenStartIndex + tokenTextLength - EMBEDDED_EXPRESSION_BEGIN.length()
);
textBlockTemplateBegin.addChild(embeddedBegin);
return textBlockTemplateBegin;
}

/**
* Builds the beginning of a string template AST.
*
Expand Down
147 changes: 147 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 @@ -6786,6 +6786,153 @@ public final class TokenTypes {
public static final int UNNAMED_PATTERN_DEF =
JavaLanguageLexer.UNNAMED_PATTERN_DEF;

/**
* The opening delimiter of a text block template. This element ({@code """}) appears
* at the beginning of a text block template.
* <p>For example:</p>
* String s = STR."""
* Hello, \{ fetchName(uuid) }!
* """;
* <pre>
* </pre>
* <p>parses as:</p>
* `--VARIABLE_DEF -&gt; VARIABLE_DEF
* |--MODIFIERS -&gt; MODIFIERS
* |--TYPE -&gt; TYPE
* | `--IDENT -&gt; String
* |--IDENT -&gt; s
* |--ASSIGN -&gt; =
* | `--EXPR -&gt; EXPR
* | `--DOT -&gt; .
* | |--IDENT -&gt; STR
* | `--TEXT_BLOCK_TEMPLATE_BEGIN -&gt; """
* | |--TEXT_BLOCK_TEMPLATE_CONTENT -&gt; \n Hello,
* | |--EMBEDDED_EXPRESSION_BEGIN -&gt; \{
* | |--EMBEDDED_EXPRESSION -&gt; EMBEDDED_EXPRESSION
* | | `--METHOD_CALL -&gt; (
* | | |--IDENT -&gt; fetchName
* | | |--ELIST -&gt; ELIST
* | | | `--EXPR -&gt; EXPR
* | | | `--IDENT -&gt; uuid
* | | `--RPAREN -&gt; )
* | |--EMBEDDED_EXPRESSION_END -&gt; }
* | |--TEXT_BLOCK_TEMPLATE_CONTENT -&gt; !\n
* | `--STRING_TEMPLATE_END -&gt; """
* `--SEMI -&gt; ;
* <pre>
* </pre>
*
* @see #TEXT_BLOCK_TEMPLATE_END
* @see #TEXT_BLOCK_TEMPLATE_CONTENT
* @see #EMBEDDED_EXPRESSION_BEGIN
* @see #EMBEDDED_EXPRESSION
* @see #EMBEDDED_EXPRESSION_END
* @see #TEXT_BLOCK_LITERAL_BEGIN
*
* @since 10.13.0
*/
public static final int TEXT_BLOCK_TEMPLATE_BEGIN =
JavaLanguageLexer.TEXT_BLOCK_TEMPLATE_BEGIN;

/**
* The closing delimiter of a text block template. This element ({@code """}) appears
* at the end of a text block template.
* <p>For example:</p>
* String s = STR."""
* Sum: \{ calculateSum(x, y) }!
* """;
* <pre>
* </pre>
* <p>parses as:</p>
* `--VARIABLE_DEF -&gt; VARIABLE_DEF
* |--MODIFIERS -&gt; MODIFIERS
* |--TYPE -&gt; TYPE
* | `--IDENT -&gt; String
* |--IDENT -&gt; s
* |--ASSIGN -&gt; =
* | `--EXPR -&gt; EXPR
* | `--DOT -&gt; .
* | |--IDENT -&gt; STR
* | `--TEXT_BLOCK_TEMPLATE_BEGIN -&gt; """
* | |--TEXT_BLOCK_TEMPLATE_CONTENT -&gt; \n Sum:
* | |--EMBEDDED_EXPRESSION_BEGIN -&gt; \{
* | |--EMBEDDED_EXPRESSION -&gt; EMBEDDED_EXPRESSION
* | | `--METHOD_CALL -&gt; (
* | | |--IDENT -&gt; calculateSum
* | | |--ELIST -&gt; ELIST
* | | | |--EXPR -&gt; EXPR
* | | | | `--IDENT -&gt; x
* | | | |--COMMA -&gt; ,
* | | | `--EXPR -&gt; EXPR
* | | | `--IDENT -&gt; y
* | | `--RPAREN -&gt; )
* | |--EMBEDDED_EXPRESSION_END -&gt; }
* | |--TEXT_BLOCK_TEMPLATE_CONTENT -&gt; !\n
* | `--STRING_TEMPLATE_END -&gt; """
* `--SEMI -&gt; ;
* <pre>
* </pre>
*
* @see #STRING_TEMPLATE_END
* @see #STRING_TEMPLATE_CONTENT
* @see #EMBEDDED_EXPRESSION_BEGIN
* @see #EMBEDDED_EXPRESSION
* @see #EMBEDDED_EXPRESSION_END
* @see #STRING_LITERAL
*
* @since 10.13.0
*/
public static final int TEXT_BLOCK_TEMPLATE_END =
JavaLanguageLexer.TEXT_BLOCK_TEMPLATE_END;

/**
* The content of a text block template. A given text block template may
* have more than one node of this type.
* <p>For example:</p>
* String s = STR."""
* dx/dy: \{ derivative("x^2") }!
* """;
* <pre>
* </pre>
* <p>parses as:</p>
* `--VARIABLE_DEF -&gt; VARIABLE_DEF
* |--MODIFIERS -&gt; MODIFIERS
* |--TYPE -&gt; TYPE
* | `--IDENT -&gt; String
* |--IDENT -&gt; s
* |--ASSIGN -&gt; =
* | `--EXPR -&gt; EXPR
* | `--DOT -&gt; .
* | |--IDENT -&gt; STR
* | `--TEXT_BLOCK_TEMPLATE_BEGIN -&gt; """
* | |--TEXT_BLOCK_TEMPLATE_CONTENT -&gt; \n dx/dy:
* | |--EMBEDDED_EXPRESSION_BEGIN -&gt; \{
* | |--EMBEDDED_EXPRESSION -&gt; EMBEDDED_EXPRESSION
* | | `--METHOD_CALL -&gt; (
* | | |--IDENT -&gt; derivative
* | | |--ELIST -&gt; ELIST
* | | | `--EXPR -&gt; EXPR
* | | | `--STRING_LITERAL -&gt; "x^2"
* | | `--RPAREN -&gt; )
* | |--EMBEDDED_EXPRESSION_END -&gt; }
* | |--TEXT_BLOCK_TEMPLATE_CONTENT -&gt; !\n
* | `--STRING_TEMPLATE_END -&gt; """
* `--SEMI -&gt; ;
* <pre>
* </pre>
*
* @see #STRING_TEMPLATE_END
* @see #STRING_TEMPLATE_CONTENT
* @see #EMBEDDED_EXPRESSION_BEGIN
* @see #EMBEDDED_EXPRESSION
* @see #EMBEDDED_EXPRESSION_END
* @see #STRING_LITERAL
*
* @since 10.13.0
*/
public static final int TEXT_BLOCK_TEMPLATE_CONTENT =
JavaLanguageLexer.TEXT_BLOCK_TEMPLATE_CONTENT;

/** Prevent instantiation. */
private TokenTypes() {
}
Expand Down

0 comments on commit d875e8f

Please sign in to comment.