Get Previous SyntaxToken

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.

Hi Johannes,

Welcome to the community!

Unfortunately, there is currently no alternative method to find the previous token of a Tree in a custom plugin. While there are better ways to implement this functionality, they rely on internal APIs that we do not want to expose. The QuickFixHelper.previousToken method you already discovered is what we use internal, with its implementation relying on JavaTree.

Your implementation could be optimized a bit though (you’re probably aware of this but wanted to provide a complete sample of the implementation principle). The previousTokenIsAnnotation method is called for every array type, but currently, it’s quite expensive. To improve efficiency, you could create the codeText only once per file and compile the regex also only once.

Best,
Marco

2 Likes

Hi Marco,
thank you for your answer. I will apply your suggestions, thanks again! :slight_smile:

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.