Search code examples
eclipsercpeclipse-jdtstring-literalsabstract-syntax-tree

How to get down to StringLiterals with Eclipse AST?


I need to create an Eclipse plugin that displays a tooltip when I hover the mouse over a String literal. But only if that String literal is the first parameter of a special method.

Here is the Test.java file I use to test my plugin:

package test;

public class Test {
    public static void main(String[] args) {
        String hello = "Hello";
        String world = Translator.get("Test.worldLabel");
        System.out.println(hello + " " + world);
    }
}

I created a class implementing IJavaEditorTextHover and I need to compile the currently edited Java file to compute if the cursor is hovering a String that needs to be translated or not.

  • Hovering "Hello" will do nothing.
  • Hovering "Test.worldLabel" will display my tooltip because that literal is included inside a Translator.get() method call.

At first I used this (170 is inside "Test.worldLabel"):

ITypeRoot typeRoot = (ITypeRoot)
    JavaUI.getEditorInputJavaElement(editorPart.getEditorInput());

JavaElement foundElement = (JavaElement) typeRoot.getElementAt(170);

But the foundElement contains the whole main() method: it is not fine-grained enough.

Then, the correct way is, I think:

private static ASTNode parse(ICompilationUnit unit, int position) {
    ASTParser parser = ASTParser.newParser(AST.JLS3);
    parser.setKind(ASTParser.K_COMPILATION_UNIT);
    parser.setSource(unit);
    parser.setResolveBindings(true);
    parser.setIgnoreMethodBodies(false);
    // TODO Future optimisation: parser.setFocalPosition(position);
    return parser.createAST((IProgressMonitor) null); // parse
}

And in my IJavaEditorTextHover.getHoverInfo(...) implementation:

ICompilationUnit compilationUnit = (ICompilationUnit)
    JavaUI.getEditorInputJavaElement(editor.getEditorInput())
int position = 170/*hoverRegion.getOffset()*/;
ASTNode ast = parse(compilationUnit, position);

And now, here is my question:

How, from this ast node, do I get the ASTNode reprensenting the StringLiteral at position 170 in the source code (the "Test.worldLabel" String)?

Bonus question: did I choose the right solution? On a performance basis.


Edit: Well, here is a solution I found:

private StringLiteral findStringLiteralAtPosition(final ASTNode parent, final int position) {

    final List<StringLiteral> stringLiterals = new ArrayList<StringLiteral>();

    parent.accept(new ASTVisitor() {
        @Override
        public boolean visit(StringLiteral stringLiteral) {
            int start = stringLiteral.getStartPosition();
            int end = start + stringLiteral.getLength();
            if (start <= position && position <= end) {
                stringLiterals.add(stringLiteral);
            }
            return super.visit(stringLiteral);
        }
    });

    return (stringLiterals.size() > 0 ? stringLiterals.get(0) : null);
}

Does it seam OK? Or is it an easier way or a more performant one?


Solution

  • One solution will be not using the offset logic at all. You can generalise the solution by using a node parent check.

    Here is a sample code:

    public boolean visit(StringLiteral stringLiteral) {
    
            // Check if parent is a method inovacation.
            if (stringLiteral.getParent().getNodeType() == ASTNode.METHOD_INVOCATION) {
    
                  // get the parent method inovacation.
                  MethodInvocation miNode = (MethodInvocation) stringLiteral.getParent();
    
                  //To do: null and empty check on argument list.
    
                  // Check if is the special method and this is the 1st argument
                  if (miNode.getName().toString().equals("SpecialMethod")
                            && miNode.arguments().get(0).toString().equals(stringLiteral.toString())) {
    
                        System.out.println("Found it : " + stringLiteral.toString());
                  }
            }
    
            return true;
        }