| OLD | NEW |
| 1 import 'package:analyzer/src/generated/ast.dart'; | 1 import 'package:analyzer/src/generated/ast.dart'; |
| 2 import 'package:analyzer/src/generated/element.dart'; |
| 2 import 'package:analyzer/src/generated/scanner.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 } |
| 3 | 21 |
| 4 /** | 22 /** |
| 5 * A CompletionTarget represents an edge in the parse tree which connects an | 23 * A CompletionTarget represents an edge in the parse tree which connects an |
| 6 * AST node (the [containingNode] of the completion) to one of its children | 24 * AST node (the [containingNode] of the completion) to one of its children |
| 7 * (the [entity], which represents the place in the parse tree where the newly | 25 * (the [entity], which represents the place in the parse tree where the newly |
| 8 * completed text will be inserted). | 26 * completed text will be inserted). |
| 9 * | 27 * |
| 10 * To illustrate, consider the following snippet of code, and its associated | 28 * To illustrate, consider the following snippet of code, and its associated |
| 11 * parse tree. (T's represent tokens, N's represent AST nodes. Some trivial | 29 * parse tree. (T's represent tokens, N's represent AST nodes. Some trivial |
| 12 * AST nodes are not shown). | 30 * AST nodes are not shown). |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 72 * | 90 * |
| 73 * Usually, the entity won't be the first child of the [containingNode] (this | 91 * Usually, the entity won't be the first child of the [containingNode] (this |
| 74 * is a consequence of placing the completion target as high in the tree as | 92 * is a consequence of placing the completion target as high in the tree as |
| 75 * possible). However, there is one exception: when the cursor is inside of | 93 * possible). However, there is one exception: when the cursor is inside of |
| 76 * a multi-character token which is not a keyword or identifier (e.g. a | 94 * a multi-character token which is not a keyword or identifier (e.g. a |
| 77 * comment, or a token like "+=", the entity will be always be the token. | 95 * comment, or a token like "+=", the entity will be always be the token. |
| 78 */ | 96 */ |
| 79 final Object entity; | 97 final Object entity; |
| 80 | 98 |
| 81 /** | 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 /** |
| 82 * Compute the appropriate [CompletionTarget] for the given [offset] within | 106 * Compute the appropriate [CompletionTarget] for the given [offset] within |
| 83 * the [compilationUnit]. | 107 * the [compilationUnit]. |
| 84 */ | 108 */ |
| 85 factory CompletionTarget.forOffset( | 109 factory CompletionTarget.forOffset( |
| 86 CompilationUnit compilationUnit, int offset) { | 110 CompilationUnit compilationUnit, int offset) { |
| 87 // The precise algorithm is as follows. We perform a depth-first search of | 111 // The precise algorithm is as follows. We perform a depth-first search of |
| 88 // all edges in the parse tree (both those that point to AST nodes and | 112 // all edges in the parse tree (both those that point to AST nodes and |
| 89 // those that point to tokens), visiting parents before children. The | 113 // those that point to tokens), visiting parents before children. The |
| 90 // first edge which points to an entity satisfying either _isCandidateToken | 114 // first edge which points to an entity satisfying either _isCandidateToken |
| 91 // or _isCandidateNode is the completion target. If no edge is found that | 115 // or _isCandidateNode is the completion target. If no edge is found that |
| (...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 157 // Since no completion target was found, we set the completion target | 181 // Since no completion target was found, we set the completion target |
| 158 // entity to null and use the compilationUnit as the parent. | 182 // entity to null and use the compilationUnit as the parent. |
| 159 return new CompletionTarget._(compilationUnit, null); | 183 return new CompletionTarget._(compilationUnit, null); |
| 160 } | 184 } |
| 161 } | 185 } |
| 162 | 186 |
| 163 /** | 187 /** |
| 164 * Create a [CompletionTarget] holding the given [containingNode] and | 188 * Create a [CompletionTarget] holding the given [containingNode] and |
| 165 * [entity]. | 189 * [entity]. |
| 166 */ | 190 */ |
| 167 CompletionTarget._(this.containingNode, this.entity); | 191 CompletionTarget._(AstNode containingNode, Object entity) |
| 192 : this.containingNode = containingNode, |
| 193 this.entity = entity, |
| 194 this.argIndex = _computeArgIndex(containingNode, entity); |
| 195 |
| 196 /** |
| 197 * Return `true` if the target is a functional argument in an argument list. |
| 198 * The target [AstNode] hierarchy *must* be resolved for this to work. |
| 199 */ |
| 200 bool isFunctionalArgument() { |
| 201 if (argIndex == null) { |
| 202 return false; |
| 203 } |
| 204 AstNode argList = containingNode; |
| 205 if (argList is! ArgumentList) { |
| 206 return false; |
| 207 } |
| 208 AstNode parent = argList.parent; |
| 209 if (parent is InstanceCreationExpression) { |
| 210 DartType instType = parent.bestType; |
| 211 if (instType != null) { |
| 212 Element intTypeElem = instType.element; |
| 213 if (intTypeElem is ClassElement) { |
| 214 SimpleIdentifier constructorName = parent.constructorName.name; |
| 215 ConstructorElement constructor = constructorName != null |
| 216 ? intTypeElem.getNamedConstructor(constructorName.name) |
| 217 : intTypeElem.unnamedConstructor; |
| 218 return constructor != null && |
| 219 _isFunctionalParameter(constructor.parameters, argIndex); |
| 220 } |
| 221 } |
| 222 } else if (parent is MethodInvocation) { |
| 223 SimpleIdentifier methodName = parent.methodName; |
| 224 if (methodName != null) { |
| 225 Element methodElem = methodName.bestElement; |
| 226 if (methodElem is MethodElement) { |
| 227 return _isFunctionalParameter(methodElem.parameters, argIndex); |
| 228 } else if (methodElem is FunctionElement) { |
| 229 return _isFunctionalParameter(methodElem.parameters, argIndex); |
| 230 } |
| 231 } |
| 232 } |
| 233 return false; |
| 234 } |
| 168 | 235 |
| 169 /** | 236 /** |
| 170 * Determine whether [node] could possibly be the [entity] for a | 237 * Determine whether [node] could possibly be the [entity] for a |
| 171 * [CompletionTarget] associated with the given [offset]. | 238 * [CompletionTarget] associated with the given [offset]. |
| 172 */ | 239 */ |
| 173 static bool _isCandidateNode(AstNode node, int offset) { | 240 static bool _isCandidateNode(AstNode node, int offset) { |
| 174 // If the node's first token is a keyword or identifier, then the node is a | 241 // If the node's first token is a keyword or identifier, then the node is a |
| 175 // candidate entity if its first token is. | 242 // candidate entity if its first token is. |
| 176 Token beginToken = node.beginToken; | 243 Token beginToken = node.beginToken; |
| 177 if (beginToken.type == TokenType.KEYWORD || | 244 if (beginToken.type == TokenType.KEYWORD || |
| (...skipping 20 matching lines...) Expand all Loading... |
| 198 if (offset < token.end) { | 265 if (offset < token.end) { |
| 199 return true; | 266 return true; |
| 200 } else if (offset == token.end) { | 267 } else if (offset == token.end) { |
| 201 return token.type == TokenType.KEYWORD || | 268 return token.type == TokenType.KEYWORD || |
| 202 token.type == TokenType.IDENTIFIER || | 269 token.type == TokenType.IDENTIFIER || |
| 203 token.length == 0; | 270 token.length == 0; |
| 204 } else { | 271 } else { |
| 205 return false; | 272 return false; |
| 206 } | 273 } |
| 207 } | 274 } |
| 275 |
| 276 /** |
| 277 * Return `true` if the parameter is a functional parameter. |
| 278 */ |
| 279 static bool _isFunctionalParameter( |
| 280 List<ParameterElement> parameters, int paramIndex) { |
| 281 if (paramIndex < parameters.length) { |
| 282 ParameterElement param = parameters[paramIndex]; |
| 283 DartType paramType = param.type; |
| 284 if (param.parameterKind == ParameterKind.NAMED) { |
| 285 // TODO(danrubel) handle named parameters |
| 286 return false; |
| 287 } else { |
| 288 return paramType is FunctionType || paramType is FunctionTypeAlias; |
| 289 } |
| 290 } |
| 291 return false; |
| 292 } |
| 208 } | 293 } |
| OLD | NEW |