/* * Copyright 2009-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.codehaus.groovy.eclipse.codeassist.requestor; import static org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistLocation.ANNOTATION_BODY; import static org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistLocation.CLASS_BODY; import static org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistLocation.EXCEPTIONS; import static org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistLocation.EXPRESSION; import static org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistLocation.EXTENDS; import static org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistLocation.IMPLEMENTS; import static org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistLocation.IMPORT; import static org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistLocation.PARAMETER; import static org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistLocation.SCRIPT; import static org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistLocation.STATEMENT; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.AnnotatedNode; import org.codehaus.groovy.ast.AnnotationNode; import org.codehaus.groovy.ast.ClassCodeVisitorSupport; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.ConstructorNode; import org.codehaus.groovy.ast.FieldNode; import org.codehaus.groovy.ast.ImportNode; import org.codehaus.groovy.ast.InnerClassNode; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.ModuleNode; import org.codehaus.groovy.ast.PackageNode; import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.ast.expr.ArrayExpression; import org.codehaus.groovy.ast.expr.BinaryExpression; import org.codehaus.groovy.ast.expr.CastExpression; import org.codehaus.groovy.ast.expr.ClassExpression; import org.codehaus.groovy.ast.expr.ClosureExpression; import org.codehaus.groovy.ast.expr.ConstantExpression; import org.codehaus.groovy.ast.expr.ConstructorCallExpression; import org.codehaus.groovy.ast.expr.DeclarationExpression; import org.codehaus.groovy.ast.expr.Expression; import org.codehaus.groovy.ast.expr.FieldExpression; import org.codehaus.groovy.ast.expr.GStringExpression; import org.codehaus.groovy.ast.expr.ListExpression; import org.codehaus.groovy.ast.expr.MapEntryExpression; import org.codehaus.groovy.ast.expr.MapExpression; import org.codehaus.groovy.ast.expr.MethodCallExpression; import org.codehaus.groovy.ast.expr.NamedArgumentListExpression; import org.codehaus.groovy.ast.expr.PropertyExpression; import org.codehaus.groovy.ast.expr.StaticMethodCallExpression; import org.codehaus.groovy.ast.expr.TupleExpression; import org.codehaus.groovy.ast.expr.VariableExpression; import org.codehaus.groovy.ast.stmt.BlockStatement; import org.codehaus.groovy.ast.stmt.CatchStatement; import org.codehaus.groovy.ast.stmt.ExpressionStatement; import org.codehaus.groovy.ast.stmt.ForStatement; import org.codehaus.groovy.ast.stmt.Statement; import org.codehaus.groovy.eclipse.core.util.VisitCompleteException; import org.codehaus.groovy.runtime.GeneratedClosure; import org.codehaus.jdt.groovy.model.GroovyCompilationUnit; import org.eclipse.jdt.groovy.core.util.GroovyUtils; /** * Finds the completion node for an offset and calculates the content assist context. */ public class CompletionNodeFinder extends ClassCodeVisitorSupport { private int completionEnd; private int completionOffset; private int supportingNodeEnd; private String completionExpression; private String fullCompletionExpression; private AnnotatedNode currentDeclaration; private GroovyCompilationUnit unit; private ContentAssistContext context; /** * Left hand side of any assignment statement or null if there is none */ private Expression lhsNode; /** * This stack keeps track of the current argument list expressions so that * we can know if a completion of a MapExpression is actually an argument * and therefore named parameters should be available. */ private final LinkedList<TupleExpression> argsStack = new LinkedList<TupleExpression>(); private final LinkedList<ASTNode> blockStack = new LinkedList<ASTNode>(); public CompletionNodeFinder( int completionOffset, int completionEnd, int supportingNodeEnd, String completionExpression, String fullCompletionExpression) { this.completionOffset = completionOffset; this.completionEnd = completionEnd; this.supportingNodeEnd = supportingNodeEnd; this.completionExpression = completionExpression; this.fullCompletionExpression = fullCompletionExpression; } public ContentAssistContext findContentAssistContext(GroovyCompilationUnit unit) { try { this.unit = unit; ModuleNode node = unit.getModuleNode(); visitImports(node); // visit script last because sometimes its source locations wrap around the other classes ClassNode script = null; for (ClassNode clazz : (Iterable<ClassNode>) node.getClasses()) { if (clazz.isScript()) { script = clazz; } else { visitClass(clazz); } } if (script != null) { visitClass(script); } } catch (VisitCompleteException e) { } return context; } @Override public void visitImports(ModuleNode node) { PackageNode packageNode = node.getPackage(); if (packageNode != null && doTest(packageNode)) { currentDeclaration = packageNode; createContext(null, packageNode, ContentAssistLocation.PACKAGE); } for (ImportNode importNode : GroovyUtils.getAllImportNodes(node)) { visitAnnotations(importNode); if (importNode.getType() != null && doTest(importNode.getType())) { currentDeclaration = importNode; createContext(null, importNode, IMPORT); } } } @Override public void visitClass(ClassNode node) { if (!doTest(node) && !node.isScript()) { return; } currentDeclaration = node; blockStack.add(node); visitAnnotations(node); blockStack.removeLast(); ClassNode supr = node.getUnresolvedSuperClass(); if (supr != null && supr.getEnd() > 0 && doTest(supr)) { createContext(null, node, EXTENDS); } Iterator<InnerClassNode> innerClasses = node.getInnerClasses(); if (innerClasses != null) { while (innerClasses.hasNext()) { InnerClassNode inner = innerClasses.next(); if (!inner.isSynthetic() || inner instanceof GeneratedClosure) { visitClass(inner); } } } if (node.getInterfaces() != null) { for (ClassNode interf : node.getInterfaces()) { if (doTest(interf)) { createContext(null, node, IMPLEMENTS); } } } for (FieldNode fn : node.getFields()) { visitField(fn); } for (ConstructorNode cn : node.getDeclaredConstructors()) { visitConstructor(cn); } for (MethodNode mn : node.getMethods()) { visitMethod(mn); } // visit <clinit> body because this is where static field initializers are placed MethodNode clinit = node.getMethod("<clinit>", Parameter.EMPTY_ARRAY); if (clinit != null && clinit.getCode() instanceof BlockStatement) { blockStack.add(clinit.getCode()); for (Statement element : (Iterable<Statement>) ((BlockStatement) clinit.getCode()).getStatements()) { element.visit(this); } blockStack.removeLast(); } // visit default constructors that have been added by the verifier; this is where initializers lie ConstructorNode init = findDefaultConstructor(node); if (init != null) { Statement statement = init.getCode(); if (statement instanceof ExpressionStatement) { ((ExpressionStatement) statement).visit(this); } else { blockStack.add(init.getCode()); for (Statement element : (Iterable<Statement>) ((BlockStatement) init.getCode()).getStatements()) { element.visit(this); } blockStack.removeLast(); } } currentDeclaration = node; for (Statement element : (Iterable<Statement>) node.getObjectInitializerStatements()) { element.visit(this); } // do the run method last since it can wrap around other methods if (node.isScript()) { MethodNode run = node.getMethod("run", Parameter.EMPTY_ARRAY); if (run != null && run.getDeclaringClass().equals(node)) { internalVisitConstructorOrMethod(run); } } // if exception has not been thrown, we are inside this class body createContext(null, node, node.isScript() ? SCRIPT : CLASS_BODY); } @Override protected void visitConstructorOrMethod(MethodNode node, boolean isConstructor) { // script body must be visited last if (!node.isScriptBody()) { internalVisitConstructorOrMethod(node); } } @Override protected void visitAnnotation(AnnotationNode annotation) { if (doTest(annotation.getClassNode())) { createContext(annotation, currentDeclaration, ContentAssistLocation.ANNOTATION); } blockStack.add(annotation); super.visitAnnotation(annotation); int annoEnd = GroovyUtils.endOffset(annotation), nameEnd = annotation.getClassNode().getEnd(); if (annoEnd > nameEnd) { // annotation has a body if (completionOffset > nameEnd && completionOffset < annoEnd) { // TODO: Is it possible to determine method node when completing a value? createContext(annotation, annotation, ContentAssistLocation.ANNOTATION_BODY); } } blockStack.removeLast(); } @Override public void visitBlockStatement(BlockStatement statement) { blockStack.add(statement); super.visitBlockStatement(statement); if (doTest(statement)) { // if we get here, then we know that we are in this block statement, // but not inside any expression. Use this to complete on createContext(blockStack.getLast(), statement, expressionOrStatement()); } blockStack.removeLast(); } // only visit the statements that may contain what we are looking for // or visit ones with unknown source locations. @Override protected void visitStatement(Statement statement) { if (doTest(statement) || (statement.getStart() <= 0 && statement.getEnd() <= 0)) { super.visitStatement(statement); } } /** * There is a special case here, where there is a completion after a method * name with no parens * * <pre> * obj.myMethodCall _ * </pre> * * In this case, the expression of the {@link ExpressionStatement} is * either a {@link PropertyExpression} or a {@link VariableExpression}. * Perhaps there are other possibilities, too, though. */ @Override public void visitExpressionStatement(ExpressionStatement statement) { super.visitExpressionStatement(statement); if (doTest(statement)) { Expression expression = statement.getExpression(); int exprEnd = expression.getEnd(); int stateEnd = statement.getEnd(); // check if either the completionoffset or the supprting node end // is in the space between the end of the expression and the end // of the statement if ((completionOffset <= stateEnd && completionOffset > exprEnd) || (supportingNodeEnd <= stateEnd && supportingNodeEnd > exprEnd)) { if (expression instanceof VariableExpression || expression instanceof ConstantExpression || expression instanceof PropertyExpression) { if (expression instanceof PropertyExpression) { expression = getRightMost(expression); } if (expression != null) { createContextForCallContext(expression, expression, expression.getText()); } } } } } @Override public void visitField(FieldNode node) { if (!doTest(node)) { return; } currentDeclaration = node; ClassNode type = node.getType(); if (type != null && doTest(type)) { createContext(null, node.getDeclaringClass(), CLASS_BODY); } blockStack.add(node); super.visitField(node); blockStack.removeLast(); // do not create a null context here. // in this case, the static initializer has moved to the <clinit> method // the end and name end comparison checks to see if there is extra // text after the name that constitutes an initializdr if (node.isStatic() && node.getEnd() > node.getNameEnd() + 1) { return; } currentDeclaration = node.getDeclaringClass(); createContext(node, node.getDeclaringClass(), CLASS_BODY); } @Override public void visitVariableExpression(VariableExpression expression) { if (doTest(expression)) { ContentAssistLocation loc = EXPRESSION; if (blockStack.getLast() instanceof AnnotationNode) { loc = ANNOTATION_BODY; } else if (supportingNodeEnd == -1) { loc = STATEMENT; } createContext(expression, blockStack.getLast(), loc); } super.visitVariableExpression(expression); } @Override public void visitFieldExpression(FieldExpression expression) { if (doTest(expression)) { createContext(expression, blockStack.getLast(), expressionOrStatement()); } super.visitFieldExpression(expression); } @Override public void visitClassExpression(ClassExpression expression) { if (doTest(expression)) { createContext(expression, blockStack.getLast(), expressionOrStatement()); } super.visitClassExpression(expression); } @Override public void visitConstantExpression(ConstantExpression expression) { if (doTest(expression)) { createContext(expression, blockStack.getLast(), expressionOrStatement()); } super.visitConstantExpression(expression); } @Override public void visitCastExpression(CastExpression node) { if (doTest(node.getType())) { createContext(node.getType(), blockStack.getLast(), expressionOrStatement()); } super.visitCastExpression(node); } @Override public void visitDeclarationExpression(DeclarationExpression expression) { ClassNode type = expression.getLeftExpression().getType(); if (doTest(type)) { createContext(type, blockStack.getLast(), expressionOrStatement()); } super.visitDeclarationExpression(expression); } @Override public void visitCatchStatement(CatchStatement statement) { internalVisitParameter(statement.getVariable(), statement); super.visitCatchStatement(statement); } @Override public void visitForLoop(ForStatement forLoop) { internalVisitParameter(forLoop.getVariable(), forLoop); super.visitForLoop(forLoop); } @Override public void visitArrayExpression(ArrayExpression expression) { if (doTest(expression)) { createContext(expression, blockStack.getLast(), expressionOrStatement()); } super.visitArrayExpression(expression); } @Override public void visitStaticMethodCallExpression(StaticMethodCallExpression expression) { if (!doTest(expression)) { return; } // don't check here if the type reference is implicit // we know that the type is not implicit if the name // location is filled in. if (expression.getOwnerType().getNameEnd() == 0 && doTest(expression.getOwnerType())) { createContext(expression.getOwnerType(), blockStack.getLast(), expressionOrStatement()); } internalVisitCallArguments(expression.getArguments(), expression); // the method itself is not an expression, but only a string if (doTest(expression)) { createContext(expression, blockStack.getLast(), expressionOrStatement()); } } @Override public void visitClosureExpression(ClosureExpression expression) { blockStack.add(expression); internalVisitParameters(expression.getParameters(), expression.getCode()); super.visitClosureExpression(expression); blockStack.removeLast(); // sometimes the code block does not end at the closing '}', but at the end of the last statement // the closure itself ends at the last '}'. So, do that test here. if (doTest(expression)) { createContext(expression, expression.getCode(), expressionOrStatement()); } } @Override public void visitBinaryExpression(BinaryExpression expression) { if (expression.getOperation().getText().equals("=")) { // keep track of the LHS expression lhsNode = expression.getLeftExpression(); } super.visitBinaryExpression(expression); lhsNode = null; if (expression.getOperation().getText().equals("[") && doTest(expression)) { // after an array access createContext(expression, blockStack.getLast(), expressionOrStatement()); } } @Override public void visitPropertyExpression(PropertyExpression expression) { if (!doTest(expression)) { return; } Expression objectExpression = expression.getObjectExpression(); Expression propertyExpression = expression.getProperty(); if (supportingNodeEnd >= 0 && supportingNodeEnd < propertyExpression.getStart()) { // GRECLIPSE-1374 probably completion after a parenthesized // expression createContext(objectExpression, blockStack.getLast(), EXPRESSION); } // check for the special case of groovy command expressions checkForCommandExpression(objectExpression, propertyExpression); super.visitPropertyExpression(expression); } @Override public void visitMethodCallExpression(MethodCallExpression expression) { if (!doTest(expression)) { return; } Expression objectExpression = expression.getObjectExpression(); Expression methodExpression = expression.getMethod(); Expression arguments = expression.getArguments(); // Could be a groovy 1.8 style command expression checkForCommandExpression(objectExpression, methodExpression); // here, we check to see if we are after the closing paren or before it. // not quite as easy as I would have hoped since the AST doesn't track // this information checkForAfterClosingParen(methodExpression, arguments); objectExpression.visit(this); if (supportingNodeEnd >= 0 && supportingNodeEnd < methodExpression.getStart()) { // GRECLIPSE-1374 probably completion after a parenthesized // expression createContext(objectExpression, blockStack.getLast(), EXPRESSION); } methodExpression.visit(this); // here do a check if we are inside of a method call, but we are not at // any particular expression (ie- this is the METHOD_CONTEXT) // there is a special case that when we are at the first character of // any expression in an argument list, // we want to do the context instead of normal completion. // do that check below internalVisitCallArguments(arguments, expression); // if we get here, then we still want to do the context // we are either at a paren, at a comma, or at a start of an expression createContextForCallContext(expression, methodExpression, /*call.isImplicitThis() ? methodExpression : objectExpression,*/ // this is not exactly right since it will fail on funky kinds of method calls, like those that are called by a GString methodExpression.getText()); } @Override public void visitConstructorCallExpression(ConstructorCallExpression expression) { if (!doTest(expression)) { return; } Expression arguments = expression.getArguments(); ClassNode constructorType = expression.getType(); checkForAfterClosingParen(expression, arguments); if (doTest(constructorType)) { createContext(constructorType, blockStack.getLast(), ContentAssistLocation.CONSTRUCTOR); } // see comments in visitMethodCallExpression internalVisitCallArguments(arguments, expression); // GRECLIPSE-1235 // determine if we are completing on the fully qualified name // assume that this is not a partially qualified name String constructorName = constructorType.getNameWithoutPackage(); if (constructorName.length() < constructorType.getLength()) { // assume fully qualified name constructorName = constructorType.getName(); } fullCompletionExpression = "new " + constructorName; createContextForCallContext(expression, constructorType, constructorName); } @Override public void visitListExpression(ListExpression expression) { super.visitListExpression(expression); if (doTest(expression)) { // completion after a list expression: []._ or [10]._ createContext(expression, currentDeclaration, EXPRESSION); } } @Override public void visitMapExpression(MapExpression expression) { super.visitMapExpression(expression); if (doTest(expression)) { // if this map is part of the enclosing ArgumentListExpression, // then assume that we are completing on named arguments if (!isArgument(expression)) { // completion after a list expression: [:]._ or [x:10]._ createContext(expression, currentDeclaration, EXPRESSION); } else { // do method context } } } @Override public void visitMapEntryExpression(MapEntryExpression expression) { // Is the completion located within the RHS of a named argument pair? if (doTest(expression.getValueExpression()) && isArgument(expression)) { lhsNode = expression.getKeyExpression(); } super.visitMapEntryExpression(expression); } @Override public void visitGStringExpression(GStringExpression expression) { visitListOfExpressions(expression.getValues()); for (ConstantExpression stringExpr : expression.getStrings()) { if (doTest(stringExpr)) { // no completions available within string literals context = null; throw new VisitCompleteException(); } } } //-------------------------------------------------------------------------- private boolean isArgument(Expression expr) { if (argsStack.isEmpty()) { return false; } TupleExpression tuple = argsStack.getLast(); return isArgument(expr, tuple.getExpressions()); } private static boolean isArgument(Expression expr, List<? extends Expression> args) { if (args != null && !args.isEmpty()) { for (Expression arg : args) { if (arg instanceof NamedArgumentListExpression) { return isArgument(expr, ((NamedArgumentListExpression) arg).getMapEntryExpressions()); } if (arg == expr) { return true; } } } return false; } /** * finds end of the last argument of an argument list expression */ private int findLastArgumentEnd(Expression args) { // need to look at the last argument expression as well as the argument list itself // problem is that closure expressions are not included in the end offset int listEnd = args.getEnd(); int lastExpressionEnd = -1; if (args instanceof TupleExpression) { TupleExpression list = (TupleExpression) args; int numExprs = list.getExpressions().size(); if (numExprs > 0) { if (listEnd == 0) { listEnd = list.getExpression(numExprs - 1).getEnd(); } Expression lastExpression = list.getExpression(numExprs - 1); lastExpressionEnd = lastExpression.getEnd(); } } return Math.max(listEnd, lastExpressionEnd); } /** * @return {@code true} iff comlpetion offset is after the last argument of the argument list */ private boolean after(int callEnd) { return (supportingNodeEnd == -1 && callEnd < completionOffset) || callEnd <= supportingNodeEnd; } /** * Visit method/constructor call arguments, but only if we are not at the * start of an expression. Otherwise, we don't do normal completion, but * only show context information. */ private void internalVisitCallArguments(Expression args, Expression call) { // check to see if we are definitely doing the context boolean doContext = false; if (args instanceof TupleExpression) { TupleExpression tuple = (TupleExpression) args; for (Expression expr : tuple.getExpressions()) { if (expr.getStart() == completionOffset) { doContext = true; break; } } argsStack.add(tuple); } else if (args != null) { if (args.getStart() == completionOffset) { doContext = true; } } if (!doContext) { // check to see if we are exactly inside of one of the arguments, ignores in between arguments args.visit(this); } if (args instanceof TupleExpression) { argsStack.removeLast(); } } private void internalVisitParameters(Parameter[] ps, ASTNode declaringNode) { if (ps != null) { for (Parameter p : ps) { internalVisitParameter(p, declaringNode); } } } private void internalVisitParameter(Parameter p, ASTNode declaringNode) { if (doTest(p.getType())) { createContext(null, declaringNode, PARAMETER); } // This is for varargs ClassNode componentType = p.getType().getComponentType(); if (componentType != null && doTest(componentType)) { createContext(null, declaringNode, PARAMETER); } Expression initialExpression = p.getInitialExpression(); if (initialExpression != null && doTest(initialExpression)) { initialExpression.visit(this); } if (p.getNameStart() < completionOffset && p.getNameEnd() >= completionOffset) { // completion on the parameter name, but should be treated as a type createContext(null, declaringNode, PARAMETER); } if (doTest(p)) { // mighe have an initial expression, but was moved out during part of the verification phase. // or might be a param without any type createContext(p, declaringNode, expressionOrStatement()); } } private void internalVisitConstructorOrMethod(MethodNode node) { if (!node.isScriptBody()) { if (!doTest(node)) { return; } currentDeclaration = node; } internalVisitParameters(node.getParameters(), node); if (node.getExceptions() != null) { for (ClassNode excep : node.getExceptions()) { if (doTest(excep)) { createContext(null, node, EXCEPTIONS); } } } blockStack.add(node); visitAnnotations(node); blockStack.removeLast(); Statement code = node.getCode(); /* * Traits have code assigned to null by the TraitASTTransformation. The * transformation creates a helper inner class node that has the * necessary data. Hence skip the rest if code is null */ if (code != null) { visitClassCodeContainer(code); if (completionOffset < code.getStart() && !node.isScriptBody()) { // probably inside an empty parameters list createContext(null, node, PARAMETER); } // if we get here, then it is probably because the block statement // has been swapped with a new one that has not had // its locations set properly code.setStart(node.getStart()); code.setEnd(node.getEnd()); createContext(code, code, expressionScriptOrStatement(node)); } } private ContentAssistLocation expressionScriptOrStatement(MethodNode node) { return node.isScriptBody() ? expressionOrScript() : expressionOrStatement(); } private ContentAssistLocation expressionOrScript() { return supportingNodeEnd == -1 ? SCRIPT : EXPRESSION; } private ContentAssistLocation expressionOrStatement() { return supportingNodeEnd == -1 ? STATEMENT : EXPRESSION; } /** * Returns rightmost expression in s property expression */ private Expression getRightMost(Expression expression) { if (expression instanceof VariableExpression || expression instanceof ConstantExpression) { return expression; } else if (expression instanceof PropertyExpression) { return getRightMost(((PropertyExpression) expression).getProperty()); } else if (expression instanceof BinaryExpression) { return getRightMost(((BinaryExpression) expression).getRightExpression()); } return null; } void checkForCommandExpression(Expression leftExpression, Expression rightExpression) { // if all of these are true, then we have a command expression: // if objectExpr is a method call with >1 arguments // if property is a constant expression // if there are no parens (note that we don't actually have to test for // this since if there are parens, then the result would still be the same) Expression leftMost = leftMost(rightExpression); if (methodCallWithOneArgument(leftExpression) && leftMost instanceof ConstantExpression && doTest(leftMost)) { createContext(leftExpression, blockStack.getLast(), EXPRESSION); } } boolean methodCallWithOneArgument(Expression objectExpression) { if (objectExpression instanceof MethodCallExpression) { Expression arguments = ((MethodCallExpression) objectExpression).getArguments(); return arguments instanceof TupleExpression && ((TupleExpression) arguments).getExpressions() != null && !((TupleExpression) arguments).getExpressions().isEmpty(); } else { return false; } } private Expression leftMost(Expression expr) { if (expr instanceof ConstantExpression) { return expr; } else if (expr instanceof PropertyExpression) { return leftMost(((PropertyExpression) expr).getObjectExpression()); } else if (expr instanceof MethodCallExpression) { return leftMost(((MethodCallExpression) expr).getObjectExpression()); } else if (expr instanceof BinaryExpression) { return leftMost(((BinaryExpression) expr).getLeftExpression()); } else { return null; } } private void checkForAfterClosingParen(AnnotatedNode contextTarget, Expression arguments) { int lastArgEnd = findLastArgumentEnd(arguments); int start = arguments.getStart(); if (start == 0 && arguments instanceof TupleExpression && !((TupleExpression) arguments).getExpressions().isEmpty()) { // TupleExpressions as argument lists do not always have slocs available start = ((TupleExpression) arguments).getExpression(0).getStart(); } if (start == 0 && lastArgEnd == 0) { // possibly a malformed constructor call with no parens return; } boolean shouldLookAtArguments = !(lastArgEnd == start && completionOffset == start); if (shouldLookAtArguments) { if (after(lastArgEnd)) { // we are at this situation (completing on 'x'): // foo().x createContext(contextTarget, blockStack.getLast(), expressionOrStatement()); } } else { // completion inside of empty argument list: // foo() // should show context, so do nothing here } } private ConstructorNode findDefaultConstructor(ClassNode node) { List<ConstructorNode> constructors = node.getDeclaredConstructors(); for (ConstructorNode constructor : constructors) { if (constructor.getParameters() == null || constructor.getParameters().length == 0) { // only return automatically generated constructors if (constructor.getEnd() <= 0) { return constructor; } } } return null; } /** * In this case, we are really completing on the method name and not * inside the parens so change the information */ private void createContextForCallContext(Expression origExpression, AnnotatedNode methodExpr, String methodName) { context = new MethodInfoContentAssistContext(completionOffset, completionExpression, fullCompletionExpression, origExpression, blockStack.getLast(), lhsNode, unit, currentDeclaration, completionEnd, methodExpr, methodName, methodExpr.getEnd()); throw new VisitCompleteException(); } private void createContext(ASTNode completionNode, ASTNode declaringNode, ContentAssistLocation location) { context = new ContentAssistContext(completionOffset, completionExpression, fullCompletionExpression, completionNode, declaringNode, lhsNode, location, unit, currentDeclaration, completionEnd); throw new VisitCompleteException(); } protected boolean doTest(ASTNode node) { return node.getEnd() > 0 && ((supportingNodeEnd > node.getStart() && supportingNodeEnd <= node.getEnd()) || (completionOffset > node.getStart() && completionOffset <= node.getEnd())); } /** * will be null if no completions are available at the location */ public ContentAssistContext getContext() { return context; } }