Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,303 changes: 369 additions & 1,934 deletions config/checker-framework-suppressions/checker-index-suppressions.xml

Large diffs are not rendered by default.

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions config/checkstyle-resources-suppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@
files="[\\/]InputNoWhitespaceBeforeTextBlocksTabIndent\.java"/>
<suppress checks="FileTabCharacter"
files="[\\/]InputJava14TextBlocksTabSize\.java"/>
<suppress checks="FileTabCharacter"
files="[\\/]InputStringTemplateBasicWithTabs\.java"/>

<!-- This file must not have a package statement, for testing purposes -->
<suppress checks="PackageDeclarationCheck"
Expand Down
14 changes: 1 addition & 13 deletions config/projects-to-test/openjdk21-excluded.files
Original file line number Diff line number Diff line change
Expand Up @@ -29,25 +29,13 @@
<property name="fileNamePattern" value="[\\/]test[\\/]langtools[\\/]tools[\\/]javac[\\/]unicode[\\/]FirstChar2.java$"/>
</module>

<!-- until https://github.com/checkstyle/checkstyle/issues/13830 -->
<!-- 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[\\/]langtools[\\/]tools[\\/]javac[\\/]diags[\\/]examples[\\/]StringTemplate.java$"/>
</module>
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="[\\/]test[\\/]micro[\\/]org[\\/]openjdk[\\/]bench[\\/]java[\\/]lang[\\/]StringTemplateFMT.java$"/>
</module>
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="[\\/]test[\\/]jdk[\\/]java[\\/]lang[\\/]template[\\/]Basic.java$"/>
</module>
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="[\\/]test[\\/]jdk[\\/]java[\\/]lang[\\/]template[\\/]FormatterBuilder.java$"/>
</module>
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="[\\/]test[\\/]langtools[\\/]tools[\\/]javac[\\/]template[\\/]T8312814.java$"/>
</module>

<!-- until https://github.com/checkstyle/checkstyle/issues/13987 -->
<module name="BeforeExecutionExclusionFileFilter">
Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1143,12 +1143,12 @@
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.81</minimum>
<minimum>0.82</minimum>
</limit>
<limit>
<counter>BRANCH</counter>
<value>COVEREDRATIO</value>
<minimum>0.79</minimum>
<minimum>0.75</minimum>
</limit>
</limits>
</rule>
Expand Down
262 changes: 261 additions & 1 deletion src/main/java/com/puppycrawl/tools/checkstyle/JavaAstVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
Expand Down Expand Up @@ -103,6 +104,15 @@ public final class JavaAstVisitor extends JavaLanguageParserBaseVisitor<DetailAs
/** String representation of the right shift operator. */
private static final String RIGHT_SHIFT = ">>";

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

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

/** String representation of the string template embedded expression ending delimiter. */
private static final String EMBEDDED_EXPRESSION_END = "}";

/**
* The tokens here are technically expressions, but should
* not return an EXPR token as their root.
Expand Down Expand Up @@ -1533,6 +1543,14 @@ public DetailAstImpl visitPrimaryExp(JavaLanguageParser.PrimaryExpContext ctx) {
return flattenedTree(ctx);
}

@Override
public DetailAstImpl visitTemplateExp(JavaLanguageParser.TemplateExpContext ctx) {
final DetailAstImpl dot = create(ctx.DOT());
dot.addChild(visit(ctx.expr()));
dot.addChild(visit(ctx.templateArgument()));
return dot;
}

@Override
public DetailAstImpl visitPostfix(JavaLanguageParser.PostfixContext ctx) {
final DetailAstImpl postfix;
Expand Down Expand Up @@ -1742,6 +1760,215 @@ public DetailAstImpl visitPrimitivePrimary(JavaLanguageParser.PrimitivePrimaryCo
return dot;
}

@Override
public DetailAstImpl visitTemplateArgument(JavaLanguageParser.TemplateArgumentContext ctx) {
return Objects.requireNonNullElseGet(visit(ctx.template()),
() -> buildSimpleStringTemplateArgument(ctx));
}

/**
* Builds a simple string template argument AST, which basically means that we
* transform a string literal into a string template AST because it is a
* string template argument.
*
* @param ctx the TemplateArgumentContext to build AST from
* @return DetailAstImpl of string template argument
*/
private static DetailAstImpl buildSimpleStringTemplateArgument(
JavaLanguageParser.TemplateArgumentContext ctx) {
final int startColumn = ctx.start.getCharPositionInLine();
final int endColumn = startColumn + ctx.getText().length() - 1;
Comment thread
rnveach marked this conversation as resolved.
final int lineNumber = ctx.start.getLine();

final DetailAstImpl templateArgument = createImaginary(
TokenTypes.STRING_TEMPLATE_BEGIN, QUOTE,
lineNumber, startColumn
);

final int quoteLength = QUOTE.length();
final int tokenTextLength = ctx.getText().length();

final String actualContent = ctx.getText()
.substring(quoteLength, tokenTextLength - quoteLength);

final DetailAstImpl content = createImaginary(
TokenTypes.STRING_TEMPLATE_CONTENT, actualContent,
lineNumber, startColumn + quoteLength
);
templateArgument.addChild(content);

final DetailAstImpl end = createImaginary(
TokenTypes.STRING_TEMPLATE_END, QUOTE,
lineNumber, endColumn
);
templateArgument.addChild(end);

return templateArgument;
}

@Override
public DetailAstImpl visitStringTemplate(JavaLanguageParser.StringTemplateContext ctx) {
final DetailAstImpl begin = buildStringTemplateBeginning(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.stringTemplateMiddle().stream()
.map(this::buildStringTemplateMiddle)
.collect(Collectors.toUnmodifiableList())
.forEach(begin::addChild);

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

/**
* Builds the beginning of a string template AST.
*
* @param ctx the StringTemplateContext to build AST from
* @return string template AST
*/
private static DetailAstImpl buildStringTemplateBeginning(
JavaLanguageParser.StringTemplateContext ctx) {

// token looks like '"' StringFragment '\{'
final TerminalNode context = ctx.STRING_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 stringTemplateBegin = createImaginary(
TokenTypes.STRING_TEMPLATE_BEGIN, QUOTE,
tokenLineNumber, tokenStartIndex
);

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

final DetailAstImpl stringTemplateContent = createImaginary(
TokenTypes.STRING_TEMPLATE_CONTENT, stringFragment,
tokenLineNumber, tokenStartIndex + QUOTE.length()
);
stringTemplateBegin.addChild(stringTemplateContent);

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

/**
* Builds the middle of a string template AST.
*
* @param middleContext the StringTemplateMiddleContext to build AST from
* @return DetailAstImpl of string template middle
*/
private DetailAstImpl buildStringTemplateMiddle(
JavaLanguageParser.StringTemplateMiddleContext middleContext) {

// token looks like '}' StringFragment '\{'
final TerminalNode context = middleContext.STRING_TEMPLATE_MID();
final Token token = context.getSymbol();
final int tokenStartIndex = token.getCharPositionInLine();
final int tokenLineNumber = token.getLine();
final String tokenText = context.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.STRING_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.expr())
.map(this::visit);

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

return embeddedExpressionEnd;
}

/**
* Builds the end of a string template AST.
*
* @param ctx the StringTemplateContext to build AST from
* @return DetailAstImpl of string template end
*/
private static DetailAstImpl buildStringTemplateEnd(
JavaLanguageParser.StringTemplateContext ctx) {

// token looks like '}' StringFragment '"'
final TerminalNode context = ctx.STRING_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 - QUOTE.length()
);

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

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

@Override
public DetailAstImpl visitCreator(JavaLanguageParser.CreatorContext ctx) {
return flattenedTree(ctx);
Expand Down Expand Up @@ -2094,7 +2321,7 @@ private void processChildren(DetailAstImpl parent, List<? extends ParseTree> chi
* should be used for imaginary nodes only, i.e. 'OBJBLOCK -&gt; OBJBLOCK',
* where the text on the RHS matches the text on the LHS.
*
* @param tokenType the token type of this DetailAstImpl
* @param tokenType the token type of this DetailAstImpl
* @return new DetailAstImpl of given type
*/
private static DetailAstImpl createImaginary(int tokenType) {
Expand All @@ -2104,6 +2331,39 @@ private static DetailAstImpl createImaginary(int tokenType) {
return detailAst;
}

/**
* Create a DetailAstImpl from a given token type and text. This method
* should be used for imaginary nodes only, i.e. 'OBJBLOCK -&gt; OBJBLOCK',
* where the text on the RHS matches the text on the LHS.
*
* @param tokenType the token type of this DetailAstImpl
* @param text the text of this DetailAstImpl
* @return new DetailAstImpl of given type
*/
private static DetailAstImpl createImaginary(int tokenType, String text) {
final DetailAstImpl imaginary = new DetailAstImpl();
imaginary.setType(tokenType);
imaginary.setText(text);
return imaginary;
}

/**
* Creates an imaginary DetailAstImpl with the given token details.
*
* @param tokenType the token type of this DetailAstImpl
* @param text the text of this DetailAstImpl
* @param lineNumber the line number of this DetailAstImpl
* @param columnNumber the column number of this DetailAstImpl
* @return imaginary DetailAstImpl from given details
*/
private static DetailAstImpl createImaginary(
int tokenType, String text, int lineNumber, int columnNumber) {
final DetailAstImpl imaginary = createImaginary(tokenType, text);
imaginary.setLineNo(lineNumber);
imaginary.setColumnNo(columnNumber);
return imaginary;
}

/**
* Create a DetailAstImpl from a given token and token type. This method
* should be used for literal nodes only, i.e. 'PACKAGE_DEF -&gt; package'.
Expand Down
Loading