| OLD | NEW | 
|---|
|  | (Empty) | 
| 1 import 'package:analyzer/src/generated/ast.dart'; |  | 
| 2 import 'package:analyzer/src/generated/element.dart'; |  | 
| 3 import 'package:analyzer/src/generated/scanner.dart'; |  | 
| 4 import 'package:analyzer/src/generated/utilities_dart.dart'; |  | 
| 5 |  | 
| 6 int _computeArgIndex(AstNode containingNode, Object entity) { |  | 
| 7   var argList = containingNode; |  | 
| 8   if (argList is ArgumentList) { |  | 
| 9     NodeList<Expression> args = argList.arguments; |  | 
| 10     for (int index = 0; index < args.length; ++index) { |  | 
| 11       if (entity == args[index]) { |  | 
| 12         return index; |  | 
| 13       } |  | 
| 14     } |  | 
| 15     if (args.isEmpty) { |  | 
| 16       return 0; |  | 
| 17     } |  | 
| 18   } |  | 
| 19   return null; |  | 
| 20 } |  | 
| 21 |  | 
| 22 /** |  | 
| 23  * A CompletionTarget represents an edge in the parse tree which connects an |  | 
| 24  * AST node (the [containingNode] of the completion) to one of its children |  | 
| 25  * (the [entity], which represents the place in the parse tree where the newly |  | 
| 26  * completed text will be inserted). |  | 
| 27  * |  | 
| 28  * To illustrate, consider the following snippet of code, and its associated |  | 
| 29  * parse tree.  (T's represent tokens, N's represent AST nodes.  Some trivial |  | 
| 30  * AST nodes are not shown). |  | 
| 31  * |  | 
| 32  *            ___N        (function declaration) |  | 
| 33  *           /    \ |  | 
| 34  *          /    __N_______  (function body) |  | 
| 35  *         /    /  |a      \ |  | 
| 36  *        /    /   N______  \  (statement) |  | 
| 37  *       /    /   /       \  \ |  | 
| 38  *      /    /   N____     \  \  (assignment expression) |  | 
| 39  *     |    /   /|    \     \  \ |  | 
| 40  *     .   |   / |    _N___  \  |  ("as" expression) |  | 
| 41  *     .   |  |  |   / |   \c | | |  | 
| 42  *     .   |  N  |  N  |b   N | |  (simple identifiers) |  | 
| 43  *         |  |  |  |  |    | | | |  | 
| 44  *         T  T  T  T  T    T T T |  | 
| 45  *     m() { foo = bar as  Baz; } |  | 
| 46  * |  | 
| 47  * The Completion target is usually placed as high in the tree as possible so |  | 
| 48  * that we can produce the most meaningful completions with minimal effort. |  | 
| 49  * For instance, if the cursor is inside the identifier "foo", the completion |  | 
| 50  * target will be the edge marked "a", so that we will produce all completions |  | 
| 51  * that could possibly start a statement, even those which would conflict with |  | 
| 52  * the current parse (such as the keyword "for", which begins a "for" |  | 
| 53  * statement).  As a consequence of this, the [entity] will usually not be the |  | 
| 54  * first child of the [containingNode] node. |  | 
| 55  * |  | 
| 56  * Note that the [containingNode] is always an AST node, but the [entity] may |  | 
| 57  * not be.  For instance, if the cursor is inside the keyword "as", the |  | 
| 58  * completion target will be the edge marked "b", so the [entity] is the token |  | 
| 59  * "as". |  | 
| 60  * |  | 
| 61  * If the cursor is between tokens, the completion target is usually associated |  | 
| 62  * with the token that follows the cursor (since that's the token that will be |  | 
| 63  * displaced when the new text is inserted).  For example, if the cursor is |  | 
| 64  * after the "{" character, then the completion target will be the edge marked |  | 
| 65  * "a", just as it is if the cursor is inside the identifier "foo".  However |  | 
| 66  * there is one exception: if the cursor is at the rightmost edge of a keyword |  | 
| 67  * or identifier, then the completion target is associated with that token, |  | 
| 68  * since any further letters typed will change the meaning of the identifier or |  | 
| 69  * keyword, rather than creating a new token.  So for instance, if the cursor |  | 
| 70  * is just after the "s" of "as", the completion target will be the edge marked |  | 
| 71  * "b", but if the cursor target is after the first space following "as", then |  | 
| 72  * the completion target will be the edge marked "c". |  | 
| 73  * |  | 
| 74  * If the file is empty, or the cursor is after all the text in the file, then |  | 
| 75  * there may be no edge in the parse tree which is appropriate to act as the |  | 
| 76  * completion target; in this case, [entity] is set to null and |  | 
| 77  * [containingNode] is set to the CompilationUnit. |  | 
| 78  */ |  | 
| 79 class CompletionTarget { |  | 
| 80   /** |  | 
| 81    * The context in which the completion is occurring.  This is the AST node |  | 
| 82    * which is a direct parent of [entity]. |  | 
| 83    */ |  | 
| 84   final AstNode containingNode; |  | 
| 85 |  | 
| 86   /** |  | 
| 87    * The entity which the completed text will replace (or which will be |  | 
| 88    * displaced once the completed text is inserted).  This may be an AstNode or |  | 
| 89    * a Token, or it may be null if the cursor is after all tokens in the file. |  | 
| 90    * |  | 
| 91    * Usually, the entity won't be the first child of the [containingNode] (this |  | 
| 92    * is a consequence of placing the completion target as high in the tree as |  | 
| 93    * possible).  However, there is one exception: when the cursor is inside of |  | 
| 94    * a multi-character token which is not a keyword or identifier (e.g. a |  | 
| 95    * comment, or a token like "+=", the entity will be always be the token. |  | 
| 96    */ |  | 
| 97   final Object entity; |  | 
| 98 |  | 
| 99   /** |  | 
| 100    * If the target is an argument in an [ArgumentList], then this is the index |  | 
| 101    * of the argument in the list, otherwise this is `null`. |  | 
| 102    */ |  | 
| 103   final int argIndex; |  | 
| 104 |  | 
| 105   /** |  | 
| 106    * Compute the appropriate [CompletionTarget] for the given [offset] within |  | 
| 107    * the [compilationUnit]. |  | 
| 108    */ |  | 
| 109   factory CompletionTarget.forOffset( |  | 
| 110       CompilationUnit compilationUnit, int offset) { |  | 
| 111     // The precise algorithm is as follows.  We perform a depth-first search of |  | 
| 112     // all edges in the parse tree (both those that point to AST nodes and |  | 
| 113     // those that point to tokens), visiting parents before children.  The |  | 
| 114     // first edge which points to an entity satisfying either _isCandidateToken |  | 
| 115     // or _isCandidateNode is the completion target.  If no edge is found that |  | 
| 116     // satisfies these two predicates, then we set the completion target entity |  | 
| 117     // to null and the containingNode to the compilationUnit. |  | 
| 118     // |  | 
| 119     // Note that if a token is not a candidate target, then none of the tokens |  | 
| 120     // that precede it are candidate targets either.  Therefore any entity |  | 
| 121     // whose last token is not a candidate target can be skipped.  This lets us |  | 
| 122     // prune the search to the point where no recursion is necessary; at each |  | 
| 123     // step in the process we know exactly which child node we need to proceed |  | 
| 124     // to. |  | 
| 125     AstNode containingNode = compilationUnit; |  | 
| 126     outerLoop: while (true) { |  | 
| 127       if (containingNode is Comment) { |  | 
| 128         // Comments are handled specially: we descend into any CommentReference |  | 
| 129         // child node that contains the cursor offset. |  | 
| 130         Comment comment = containingNode; |  | 
| 131         for (CommentReference commentReference in comment.references) { |  | 
| 132           if (commentReference.offset <= offset && |  | 
| 133               offset <= commentReference.end) { |  | 
| 134             containingNode = commentReference; |  | 
| 135             continue outerLoop; |  | 
| 136           } |  | 
| 137         } |  | 
| 138       } |  | 
| 139       for (var entity in containingNode.childEntities) { |  | 
| 140         if (entity is Token) { |  | 
| 141           if (_isCandidateToken(entity, offset)) { |  | 
| 142             // Target found. |  | 
| 143             return new CompletionTarget._(containingNode, entity); |  | 
| 144           } else { |  | 
| 145             // Since entity is a token, we don't need to look inside it; just |  | 
| 146             // proceed to the next entity. |  | 
| 147             continue; |  | 
| 148           } |  | 
| 149         } else if (entity is AstNode) { |  | 
| 150           // If the last token in the node isn't a candidate target, then |  | 
| 151           // neither the node nor any of its descendants can possibly be the |  | 
| 152           // completion target, so we can skip the node entirely. |  | 
| 153           if (!_isCandidateToken(entity.endToken, offset)) { |  | 
| 154             continue; |  | 
| 155           } |  | 
| 156 |  | 
| 157           // If the node is a candidate target, then we are done. |  | 
| 158           if (_isCandidateNode(entity, offset)) { |  | 
| 159             // Check to see if the offset is in a preceeding comment |  | 
| 160             Token commentToken = _getContainingCommentToken(entity, offset); |  | 
| 161             if (commentToken != null) { |  | 
| 162               entity = commentToken; |  | 
| 163               // If the preceeding comment is dartdoc token then update |  | 
| 164               // the containing node to be the dartdoc comment |  | 
| 165               Comment docComment = |  | 
| 166                   _getContainingDocComment(containingNode, commentToken); |  | 
| 167               if (docComment != null) { |  | 
| 168                 containingNode = docComment; |  | 
| 169               } |  | 
| 170             } |  | 
| 171             return new CompletionTarget._(containingNode, entity); |  | 
| 172           } |  | 
| 173 |  | 
| 174           // Otherwise, the completion target is somewhere inside the entity, |  | 
| 175           // so we need to jump to the start of the outer loop to examine its |  | 
| 176           // contents. |  | 
| 177           containingNode = entity; |  | 
| 178           continue outerLoop; |  | 
| 179         } else { |  | 
| 180           // Unexpected entity found (all entities in a parse tree should be |  | 
| 181           // AST nodes or tokens). |  | 
| 182           assert(false); |  | 
| 183         } |  | 
| 184       } |  | 
| 185 |  | 
| 186       // No completion target found.  It should only be possible to reach here |  | 
| 187       // the first time through the outer loop (since we only jump to the start |  | 
| 188       // of the outer loop after determining that the completion target is |  | 
| 189       // inside an entity).  We can check that assumption by verifying that |  | 
| 190       // containingNode is still the compilationUnit. |  | 
| 191       assert(identical(containingNode, compilationUnit)); |  | 
| 192 |  | 
| 193       // Since no completion target was found, we set the completion target |  | 
| 194       // entity to null and use the compilationUnit as the parent. |  | 
| 195       return new CompletionTarget._(compilationUnit, null); |  | 
| 196     } |  | 
| 197   } |  | 
| 198 |  | 
| 199   /** |  | 
| 200    * Create a [CompletionTarget] holding the given [containingNode] and |  | 
| 201    * [entity]. |  | 
| 202    */ |  | 
| 203   CompletionTarget._(AstNode containingNode, Object entity) |  | 
| 204       : this.containingNode = containingNode, |  | 
| 205         this.entity = entity, |  | 
| 206         this.argIndex = _computeArgIndex(containingNode, entity); |  | 
| 207 |  | 
| 208   /** |  | 
| 209    * Return `true` if the target is a functional argument in an argument list. |  | 
| 210    * The target [AstNode] hierarchy *must* be resolved for this to work. |  | 
| 211    */ |  | 
| 212   bool isFunctionalArgument() { |  | 
| 213     if (argIndex == null) { |  | 
| 214       return false; |  | 
| 215     } |  | 
| 216     AstNode argList = containingNode; |  | 
| 217     if (argList is! ArgumentList) { |  | 
| 218       return false; |  | 
| 219     } |  | 
| 220     AstNode parent = argList.parent; |  | 
| 221     if (parent is InstanceCreationExpression) { |  | 
| 222       DartType instType = parent.bestType; |  | 
| 223       if (instType != null) { |  | 
| 224         Element intTypeElem = instType.element; |  | 
| 225         if (intTypeElem is ClassElement) { |  | 
| 226           SimpleIdentifier constructorName = parent.constructorName.name; |  | 
| 227           ConstructorElement constructor = constructorName != null |  | 
| 228               ? intTypeElem.getNamedConstructor(constructorName.name) |  | 
| 229               : intTypeElem.unnamedConstructor; |  | 
| 230           return constructor != null && |  | 
| 231               _isFunctionalParameter(constructor.parameters, argIndex); |  | 
| 232         } |  | 
| 233       } |  | 
| 234     } else if (parent is MethodInvocation) { |  | 
| 235       SimpleIdentifier methodName = parent.methodName; |  | 
| 236       if (methodName != null) { |  | 
| 237         Element methodElem = methodName.bestElement; |  | 
| 238         if (methodElem is MethodElement) { |  | 
| 239           return _isFunctionalParameter(methodElem.parameters, argIndex); |  | 
| 240         } else if (methodElem is FunctionElement) { |  | 
| 241           return _isFunctionalParameter(methodElem.parameters, argIndex); |  | 
| 242         } |  | 
| 243       } |  | 
| 244     } |  | 
| 245     return false; |  | 
| 246   } |  | 
| 247 |  | 
| 248   /** |  | 
| 249    * Determine if the offset is contained in a preceeding comment token |  | 
| 250    * and return that token, otherwise return `null`. |  | 
| 251    */ |  | 
| 252   static Token _getContainingCommentToken(AstNode node, int offset) { |  | 
| 253     if (offset >= node.offset) { |  | 
| 254       return null; |  | 
| 255     } |  | 
| 256     Token token = node.beginToken; |  | 
| 257     if (token == null) { |  | 
| 258       return null; |  | 
| 259     } |  | 
| 260     token = token.precedingComments; |  | 
| 261     while (token != null) { |  | 
| 262       if (offset <= token.offset) { |  | 
| 263         return null; |  | 
| 264       } |  | 
| 265       if (offset <= token.end) { |  | 
| 266         if (token.type == TokenType.SINGLE_LINE_COMMENT || offset < token.end) { |  | 
| 267           return token; |  | 
| 268         } |  | 
| 269       } |  | 
| 270       token = token.next; |  | 
| 271     } |  | 
| 272     return null; |  | 
| 273   } |  | 
| 274 |  | 
| 275   /** |  | 
| 276    * Determine if the given token is part of the given node's dart doc. |  | 
| 277    */ |  | 
| 278   static Comment _getContainingDocComment(AstNode node, Token token) { |  | 
| 279     if (node is AnnotatedNode) { |  | 
| 280       Comment docComment = node.documentationComment; |  | 
| 281       if (docComment != null && docComment.tokens.contains(token)) { |  | 
| 282         return docComment; |  | 
| 283       } |  | 
| 284     } |  | 
| 285     return null; |  | 
| 286   } |  | 
| 287 |  | 
| 288   /** |  | 
| 289    * Determine whether [node] could possibly be the [entity] for a |  | 
| 290    * [CompletionTarget] associated with the given [offset]. |  | 
| 291    */ |  | 
| 292   static bool _isCandidateNode(AstNode node, int offset) { |  | 
| 293     // If the node's first token is a keyword or identifier, then the node is a |  | 
| 294     // candidate entity if its first token is. |  | 
| 295     Token beginToken = node.beginToken; |  | 
| 296     if (beginToken.type == TokenType.KEYWORD || |  | 
| 297         beginToken.type == TokenType.IDENTIFIER) { |  | 
| 298       return _isCandidateToken(beginToken, offset); |  | 
| 299     } |  | 
| 300 |  | 
| 301     // Otherwise, the node is a candidate entity only if the offset is before |  | 
| 302     // the beginning of the node.  This ensures that completions within a token |  | 
| 303     // (e.g. inside a literal string or inside a comment) are evaluated within |  | 
| 304     // the context of the token itself. |  | 
| 305     return offset <= node.offset; |  | 
| 306   } |  | 
| 307 |  | 
| 308   /** |  | 
| 309    * Determine whether [token] could possibly be the [entity] for a |  | 
| 310    * [CompletionTarget] associated with the given [offset]. |  | 
| 311    */ |  | 
| 312   static bool _isCandidateToken(Token token, int offset) { |  | 
| 313     // A token is considered a candidate entity if the cursor offset is (a) |  | 
| 314     // before the start of the token, (b) within the token, (c) at the end of |  | 
| 315     // the token and the token is a keyword or identifier, or (d) at the |  | 
| 316     // location of the token and the token is zero length. |  | 
| 317     if (offset < token.end) { |  | 
| 318       return true; |  | 
| 319     } else if (offset == token.end) { |  | 
| 320       return token.type == TokenType.KEYWORD || |  | 
| 321           token.type == TokenType.IDENTIFIER || |  | 
| 322           token.length == 0; |  | 
| 323     } else if (!token.isSynthetic) { |  | 
| 324       return false; |  | 
| 325     } |  | 
| 326     // If the current token is synthetic, then check the previous token |  | 
| 327     // because it may have been dropped from the parse tree |  | 
| 328     Token previous = token.previous; |  | 
| 329     if (offset < previous.end) { |  | 
| 330       return true; |  | 
| 331     } else if (offset == previous.end) { |  | 
| 332       return token.type == TokenType.KEYWORD || |  | 
| 333           previous.type == TokenType.IDENTIFIER; |  | 
| 334     } else { |  | 
| 335       return false; |  | 
| 336     } |  | 
| 337   } |  | 
| 338 |  | 
| 339   /** |  | 
| 340    * Return `true` if the parameter is a functional parameter. |  | 
| 341    */ |  | 
| 342   static bool _isFunctionalParameter( |  | 
| 343       List<ParameterElement> parameters, int paramIndex) { |  | 
| 344     if (paramIndex < parameters.length) { |  | 
| 345       ParameterElement param = parameters[paramIndex]; |  | 
| 346       DartType paramType = param.type; |  | 
| 347       if (param.parameterKind == ParameterKind.NAMED) { |  | 
| 348         // TODO(danrubel) handle named parameters |  | 
| 349         return false; |  | 
| 350       } else { |  | 
| 351         return paramType is FunctionType || paramType is FunctionTypeAlias; |  | 
| 352       } |  | 
| 353     } |  | 
| 354     return false; |  | 
| 355   } |  | 
| 356 } |  | 
| OLD | NEW | 
|---|