# HG changeset patch # User sundar # Date 1439904193 -19800 # Node ID 477207d0b807bc8ebd5557d78636891b88e62d17 # Parent 54af83b4a714633ef04f3d3edbe8c27dd95ecf90 8133812: identifier and member expression completion handling is not uniform Reviewed-by: attila, hannesw diff -r 54af83b4a714 -r 477207d0b807 src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java --- a/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java Tue Aug 18 11:40:18 2015 +0530 +++ b/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java Tue Aug 18 18:53:13 2015 +0530 @@ -26,6 +26,7 @@ package jdk.nashorn.tools.jjs; import java.util.List; +import java.util.regex.Pattern; import jdk.internal.jline.console.completer.Completer; import jdk.nashorn.api.tree.AssignmentTree; import jdk.nashorn.api.tree.BinaryTree; @@ -34,8 +35,11 @@ import jdk.nashorn.api.tree.ConditionalExpressionTree; import jdk.nashorn.api.tree.ExpressionTree; import jdk.nashorn.api.tree.ExpressionStatementTree; +import jdk.nashorn.api.tree.FunctionCallTree; +import jdk.nashorn.api.tree.IdentifierTree; import jdk.nashorn.api.tree.InstanceOfTree; import jdk.nashorn.api.tree.MemberSelectTree; +import jdk.nashorn.api.tree.NewTree; import jdk.nashorn.api.tree.SimpleTreeVisitorES5_1; import jdk.nashorn.api.tree.Tree; import jdk.nashorn.api.tree.UnaryTree; @@ -57,6 +61,10 @@ this.parser = Parser.create(); } + // Pattern to match a unfinished member selection expression. object part and "." + // but property name missing pattern. + private static final Pattern SELECT_PROP_MISSING = Pattern.compile(".*\\.\\s*"); + @Override public int complete(final String test, final int cursor, final List result) { // check that cursor is at the end of test string. Do not complete in the middle! @@ -64,65 +72,68 @@ return cursor; } - // if it has a ".", then assume it is a member selection expression - final int idx = test.lastIndexOf('.'); - if (idx == -1) { - if (isIdentifier(test)) { - // identifier - return matching global variable names, if any - result.addAll(PropertiesHelper.getProperties(global, test)); - return idx + 1; - } + // do we have an incomplete member selection expression that misses property name? + final boolean endsWithDot = SELECT_PROP_MISSING.matcher(test).matches(); - return cursor; - } + // If this is an incomplete member selection, then it is not legal code + // Make it legal by adding a random property name "x" to it. + final String exprToEval = endsWithDot? test + "x" : test; - // stuff before the last "." - final String exprBeforeDot = test.substring(0, idx); - - // Make sure that completed code will have a member expression! Adding ".x" as a - // random property/field name selected to make it possible to be a proper member select - final ExpressionTree topExpr = getTopLevelExpression(parser, exprBeforeDot + ".x"); + final ExpressionTree topExpr = getTopLevelExpression(parser, exprToEval); if (topExpr == null) { // did not parse to be a top level expression, no suggestions! return cursor; } - // Find 'right most' member select expression's start position - final int startPosition = (int) getStartOfMemberSelect(topExpr); - if (startPosition == -1) { - // not a member expression that we can handle for completion + // Find 'right most' expression of the top level expression + final Tree rightMostExpr = getRightMostExpression(topExpr); + if (rightMostExpr instanceof MemberSelectTree) { + return completeMemberSelect(test, cursor, result, (MemberSelectTree)rightMostExpr, endsWithDot); + } else if (rightMostExpr instanceof IdentifierTree) { + return completeIdentifier(test, cursor, result, (IdentifierTree)rightMostExpr); + } else { + // expression that we cannot handle for completion return cursor; } + } - // The part of the right most member select expression before the "." - final String objExpr = test.substring(startPosition, idx); + private int completeMemberSelect(final String test, final int cursor, final List result, + final MemberSelectTree select, final boolean endsWithDot) { + final ExpressionTree objExpr = select.getExpression(); + final String objExprCode = test.substring((int)objExpr.getStartPosition(), (int)objExpr.getEndPosition()); // try to evaluate the object expression part as a script Object obj = null; try { - obj = context.eval(global, objExpr, global, ""); + obj = context.eval(global, objExprCode, global, ""); } catch (Exception ignored) { // throw the exception - this is during tab-completion } if (obj != null && obj != ScriptRuntime.UNDEFINED) { - // where is the last dot? Is there a partial property name specified? - final String prefix = test.substring(idx + 1); - if (prefix.isEmpty()) { + if (endsWithDot) { // no user specified "prefix". List all properties of the object result.addAll(PropertiesHelper.getProperties(obj)); return cursor; } else { // list of properties matching the user specified prefix + final String prefix = select.getIdentifier(); result.addAll(PropertiesHelper.getProperties(obj, prefix)); - return idx + 1; + return cursor - prefix.length(); } } return cursor; } + private int completeIdentifier(final String test, final int cursor, final List result, + final IdentifierTree ident) { + final String name = ident.getName(); + result.addAll(PropertiesHelper.getProperties(global, name)); + return cursor - name.length(); + } + // returns ExpressionTree if the given code parses to a top level expression. // Or else returns null. private ExpressionTree getTopLevelExpression(final Parser parser, final String code) { @@ -143,65 +154,63 @@ return null; } - - private long getStartOfMemberSelect(final ExpressionTree expr) { - if (expr instanceof MemberSelectTree) { - return ((MemberSelectTree)expr).getStartPosition(); - } - - final Tree rightMostExpr = expr.accept(new SimpleTreeVisitorES5_1() { + private Tree getRightMostExpression(final ExpressionTree expr) { + return expr.accept(new SimpleTreeVisitorES5_1() { @Override public Tree visitAssignment(final AssignmentTree at, final Void v) { - return at.getExpression(); + return getRightMostExpression(at.getExpression()); } @Override public Tree visitCompoundAssignment(final CompoundAssignmentTree cat, final Void v) { - return cat.getExpression(); + return getRightMostExpression(cat.getExpression()); } @Override public Tree visitConditionalExpression(final ConditionalExpressionTree cet, final Void v) { - return cet.getFalseExpression(); + return getRightMostExpression(cet.getFalseExpression()); } @Override public Tree visitBinary(final BinaryTree bt, final Void v) { - return bt.getRightOperand(); + return getRightMostExpression(bt.getRightOperand()); } @Override + public Tree visitIdentifier(final IdentifierTree ident, final Void v) { + return ident; + } + + + @Override public Tree visitInstanceOf(final InstanceOfTree it, final Void v) { return it.getType(); } + + @Override + public Tree visitMemberSelect(final MemberSelectTree select, final Void v) { + return select; + } + + @Override + public Tree visitNew(final NewTree nt, final Void v) { + final ExpressionTree call = nt.getConstructorExpression(); + if (call instanceof FunctionCallTree) { + final ExpressionTree func = ((FunctionCallTree)call).getFunctionSelect(); + // Is this "new Foo" or "new obj.Foo" with no user arguments? + // If so, we may be able to do completion of constructor name. + if (func.getEndPosition() == nt.getEndPosition()) { + return func; + } + } + return null; + } + @Override public Tree visitUnary(final UnaryTree ut, final Void v) { - return ut.getExpression(); + return getRightMostExpression(ut.getExpression()); } }, null); - - return (rightMostExpr instanceof MemberSelectTree)? - rightMostExpr.getStartPosition() : -1L; - } - - // return if the given String is a valid identifier name or not - private boolean isIdentifier(final String test) { - if (test.isEmpty()) { - return false; - } - - final char[] buf = test.toCharArray(); - if (! Character.isJavaIdentifierStart(buf[0])) { - return false; - } - - for (int idx = 1; idx < buf.length; idx++) { - if (! Character.isJavaIdentifierPart(buf[idx])) { - return false; - } - } - - return true; } }