Hello,
I am currently re-implementing a Checkstyle rule named “NoWhitespacesAfter” for SonarQube. I know about the Sonar-Checkstyle-Bridge but as this bridge is a third-party plugin it does not work for SonarLint which is necessary in my case.
In the Checkstyle Check, there is an edge case where whitespaces before array brackets are allowed if an annotation exists left from it
char @NotNull [] param
So I would like to check something like “Is the previous SyntaxToken of kind annotation?”. I already did so by:
1. reading out the file content as a String with “context.getFileLines()”
2. creating a subString from 0 to the token’s position
3. Using a regex to check if there is an annotation at the end of the subString
Please check the code snippet for details.
public class NoWhitespaceAfterCheck extends IssuableSubscriptionVisitor {
@Override
public List<Kind> nodesToVisit() {
return Arrays.asList(Kind.ARRAY_TYPE);
}
@Override
public void visitNode(Tree pTree) {
final ArrayTypeTree arrayTypeTree = (ArrayTypeTree) pTree;
SyntaxToken closeBracketToken = arrayTypeTree.closeBracketToken();
boolean previousTokenIsAnnotation = previousTokenIsAnnotation(closeBracketToken);
System.out.println(previousTokenIsAnnotation);
}
private boolean previousTokenIsAnnotation(SyntaxToken pToken) {
String codeText = context.getFileLines().stream().collect(Collectors.joining(System.lineSeparator()));
int rightBracketIndex = findCharIndex(codeText, pToken);
int leftBracketIndex = rightBracketIndex - 1;
String subString = codeText.substring(0, leftBracketIndex);
String regexEndingAnnotation = ".*@\\w*(\\s*)?$";
Pattern pattern = Pattern.compile(regexEndingAnnotation, Pattern.DOTALL);
Matcher matcher = pattern.matcher(subString);
return matcher.find();
}
private int findCharIndex(String pText, SyntaxToken pToken) {
int column = pToken.range().start().column();
int line = pToken.range().start().line() - 1;
String[] lines = pText.split(System.lineSeparator());
int codePointIndex = 0;
for (int i = 0; i < line; i++) {
codePointIndex += lines[i].length() + System.lineSeparator().length();
}
codePointIndex += column;
return codePointIndex - 1;
}
}
But writing this code feels wrong because I would suspect the SonarQube API to have a method like “getPreviousToken(SyntaxToken token)” for such a standard scenario. Am I missing something here?
There is an example of such a method in the sonar-java repository which uses the “JavaTree” class which unfortunately is not available at runtime as many useful classes are, as it is said on the CUSTOM_RULES page. However, it is also written on that page’s chapter’s last sentence that it’s possible to suggest new features. I think an often-used feature would be to find neighboring SyntaxTokens more conveniently with such a method. What is the reason why “JavaTree” is not part of the API - would it slow down the execution of a rule? But maybe I am missing something the SonarQube API already provides.