| OLD | NEW |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library analysis_server.src.provisional.completion.dart.completion_target; | 5 library analysis_server.src.provisional.completion.dart.completion_target; |
| 6 | 6 |
| 7 import 'package:analyzer/dart/ast/ast.dart'; | 7 import 'package:analyzer/dart/ast/ast.dart'; |
| 8 import 'package:analyzer/dart/ast/token.dart'; | 8 import 'package:analyzer/dart/ast/token.dart'; |
| 9 import 'package:analyzer/dart/element/element.dart'; | 9 import 'package:analyzer/dart/element/element.dart'; |
| 10 import 'package:analyzer/dart/element/type.dart'; | 10 import 'package:analyzer/dart/element/type.dart'; |
| 11 import 'package:analyzer/src/generated/utilities_dart.dart'; | 11 import 'package:analyzer/src/generated/utilities_dart.dart'; |
| 12 | 12 |
| 13 int _computeArgIndex(AstNode containingNode, Object entity) { | 13 int _computeArgIndex(AstNode containingNode, Object entity) { |
| 14 var argList = containingNode; | 14 var argList = containingNode; |
| 15 if (argList is NamedExpression) { |
| 16 entity = argList; |
| 17 argList = argList.parent; |
| 18 } |
| 15 if (argList is ArgumentList) { | 19 if (argList is ArgumentList) { |
| 16 NodeList<Expression> args = argList.arguments; | 20 NodeList<Expression> args = argList.arguments; |
| 17 for (int index = 0; index < args.length; ++index) { | 21 for (int index = 0; index < args.length; ++index) { |
| 18 if (entity == args[index]) { | 22 if (entity == args[index]) { |
| 19 return index; | 23 return index; |
| 20 } | 24 } |
| 21 } | 25 } |
| 22 if (args.isEmpty) { | 26 if (args.isEmpty) { |
| 23 return 0; | 27 return 0; |
| 24 } | 28 } |
| (...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 130 | 134 |
| 131 /** | 135 /** |
| 132 * If the target is an argument in an [ArgumentList], then this is the index | 136 * If the target is an argument in an [ArgumentList], then this is the index |
| 133 * of the argument in the list, otherwise this is `null`. | 137 * of the argument in the list, otherwise this is `null`. |
| 134 */ | 138 */ |
| 135 final int argIndex; | 139 final int argIndex; |
| 136 | 140 |
| 137 /** | 141 /** |
| 138 * Compute the appropriate [CompletionTarget] for the given [offset] within | 142 * Compute the appropriate [CompletionTarget] for the given [offset] within |
| 139 * the [compilationUnit]. | 143 * the [compilationUnit]. |
| 140 * | 144 * |
| 141 * Optionally, start the search from within [entryPoint] instead of using | 145 * Optionally, start the search from within [entryPoint] instead of using |
| 142 * the [compilationUnit], which is useful for analyzing ASTs that have no | 146 * the [compilationUnit], which is useful for analyzing ASTs that have no |
| 143 * [compilationUnit] such as dart expressions within angular templates. | 147 * [compilationUnit] such as dart expressions within angular templates. |
| 144 */ | 148 */ |
| 145 factory CompletionTarget.forOffset( | 149 factory CompletionTarget.forOffset( |
| 146 CompilationUnit compilationUnit, int offset, {AstNode entryPoint}) { | 150 CompilationUnit compilationUnit, int offset, |
| 151 {AstNode entryPoint}) { |
| 147 // The precise algorithm is as follows. We perform a depth-first search of | 152 // The precise algorithm is as follows. We perform a depth-first search of |
| 148 // all edges in the parse tree (both those that point to AST nodes and | 153 // all edges in the parse tree (both those that point to AST nodes and |
| 149 // those that point to tokens), visiting parents before children. The | 154 // those that point to tokens), visiting parents before children. The |
| 150 // first edge which points to an entity satisfying either _isCandidateToken | 155 // first edge which points to an entity satisfying either _isCandidateToken |
| 151 // or _isCandidateNode is the completion target. If no edge is found that | 156 // or _isCandidateNode is the completion target. If no edge is found that |
| 152 // satisfies these two predicates, then we set the completion target entity | 157 // satisfies these two predicates, then we set the completion target entity |
| 153 // to null and the containingNode to the entryPoint. | 158 // to null and the containingNode to the entryPoint. |
| 154 // | 159 // |
| 155 // Note that if a token is not a candidate target, then none of the tokens | 160 // Note that if a token is not a candidate target, then none of the tokens |
| 156 // that precede it are candidate targets either. Therefore any entity | 161 // that precede it are candidate targets either. Therefore any entity |
| 157 // whose last token is not a candidate target can be skipped. This lets us | 162 // whose last token is not a candidate target can be skipped. This lets us |
| 158 // prune the search to the point where no recursion is necessary; at each | 163 // prune the search to the point where no recursion is necessary; at each |
| 159 // step in the process we know exactly which child node we need to proceed | 164 // step in the process we know exactly which child node we need to proceed |
| 160 // to. | 165 // to. |
| 161 entryPoint ??= compilationUnit; | 166 entryPoint ??= compilationUnit; |
| 162 AstNode containingNode = entryPoint; | 167 AstNode containingNode = entryPoint; |
| 163 outerLoop: while (true) { | 168 outerLoop: |
| 169 while (true) { |
| 164 if (containingNode is Comment) { | 170 if (containingNode is Comment) { |
| 165 // Comments are handled specially: we descend into any CommentReference | 171 // Comments are handled specially: we descend into any CommentReference |
| 166 // child node that contains the cursor offset. | 172 // child node that contains the cursor offset. |
| 167 Comment comment = containingNode; | 173 Comment comment = containingNode; |
| 168 for (CommentReference commentReference in comment.references) { | 174 for (CommentReference commentReference in comment.references) { |
| 169 if (commentReference.offset <= offset && | 175 if (commentReference.offset <= offset && |
| 170 offset <= commentReference.end) { | 176 offset <= commentReference.end) { |
| 171 containingNode = commentReference; | 177 containingNode = commentReference; |
| 172 continue outerLoop; | 178 continue outerLoop; |
| 173 } | 179 } |
| (...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 277 /** | 283 /** |
| 278 * Return `true` if the target is a functional argument in an argument list. | 284 * Return `true` if the target is a functional argument in an argument list. |
| 279 * The target [AstNode] hierarchy *must* be resolved for this to work. | 285 * The target [AstNode] hierarchy *must* be resolved for this to work. |
| 280 * See [maybeFunctionalArgument]. | 286 * See [maybeFunctionalArgument]. |
| 281 */ | 287 */ |
| 282 bool isFunctionalArgument() { | 288 bool isFunctionalArgument() { |
| 283 if (!maybeFunctionalArgument()) { | 289 if (!maybeFunctionalArgument()) { |
| 284 return false; | 290 return false; |
| 285 } | 291 } |
| 286 AstNode parent = containingNode.parent; | 292 AstNode parent = containingNode.parent; |
| 293 if (parent is ArgumentList) { |
| 294 parent = parent.parent; |
| 295 } |
| 287 if (parent is InstanceCreationExpression) { | 296 if (parent is InstanceCreationExpression) { |
| 288 DartType instType = parent.bestType; | 297 DartType instType = parent.bestType; |
| 289 if (instType != null) { | 298 if (instType != null) { |
| 290 Element intTypeElem = instType.element; | 299 Element intTypeElem = instType.element; |
| 291 if (intTypeElem is ClassElement) { | 300 if (intTypeElem is ClassElement) { |
| 292 SimpleIdentifier constructorName = parent.constructorName.name; | 301 SimpleIdentifier constructorName = parent.constructorName.name; |
| 293 ConstructorElement constructor = constructorName != null | 302 ConstructorElement constructor = constructorName != null |
| 294 ? intTypeElem.getNamedConstructor(constructorName.name) | 303 ? intTypeElem.getNamedConstructor(constructorName.name) |
| 295 : intTypeElem.unnamedConstructor; | 304 : intTypeElem.unnamedConstructor; |
| 296 return constructor != null && | 305 return constructor != null && |
| 297 _isFunctionalParameter(constructor.parameters, argIndex); | 306 _isFunctionalParameter( |
| 307 constructor.parameters, argIndex, containingNode); |
| 298 } | 308 } |
| 299 } | 309 } |
| 300 } else if (parent is MethodInvocation) { | 310 } else if (parent is MethodInvocation) { |
| 301 SimpleIdentifier methodName = parent.methodName; | 311 SimpleIdentifier methodName = parent.methodName; |
| 302 if (methodName != null) { | 312 if (methodName != null) { |
| 303 Element methodElem = methodName.bestElement; | 313 Element methodElem = methodName.bestElement; |
| 304 if (methodElem is MethodElement) { | 314 if (methodElem is MethodElement) { |
| 305 return _isFunctionalParameter(methodElem.parameters, argIndex); | 315 return _isFunctionalParameter( |
| 316 methodElem.parameters, argIndex, containingNode); |
| 306 } else if (methodElem is FunctionElement) { | 317 } else if (methodElem is FunctionElement) { |
| 307 return _isFunctionalParameter(methodElem.parameters, argIndex); | 318 return _isFunctionalParameter( |
| 319 methodElem.parameters, argIndex, containingNode); |
| 308 } | 320 } |
| 309 } | 321 } |
| 310 } | 322 } |
| 311 return false; | 323 return false; |
| 312 } | 324 } |
| 313 | 325 |
| 314 /** | 326 /** |
| 315 * Return `true` if the target maybe a functional argument in an argument list
. | 327 * Return `true` if the target maybe a functional argument in an argument list
. |
| 316 * This is used in determining whether the target [AstNode] hierarchy | 328 * This is used in determining whether the target [AstNode] hierarchy |
| 317 * needs to be resolved so that [isFunctionalArgument] will work. | 329 * needs to be resolved so that [isFunctionalArgument] will work. |
| 318 */ | 330 */ |
| 319 bool maybeFunctionalArgument() { | 331 bool maybeFunctionalArgument() { |
| 320 if (argIndex == null) { | 332 if (argIndex != null) { |
| 321 return false; | 333 if (containingNode is ArgumentList) { |
| 334 return true; |
| 335 } |
| 336 if (containingNode is NamedExpression) { |
| 337 if (containingNode.parent is ArgumentList) { |
| 338 return true; |
| 339 } |
| 340 } |
| 322 } | 341 } |
| 323 AstNode argList = containingNode; | 342 return false; |
| 324 if (argList is! ArgumentList) { | |
| 325 return false; | |
| 326 } | |
| 327 return true; | |
| 328 } | 343 } |
| 329 | 344 |
| 330 /** | 345 /** |
| 331 * Determine if the offset is contained in a preceding comment token | 346 * Determine if the offset is contained in a preceding comment token |
| 332 * and return that token, otherwise return `null`. | 347 * and return that token, otherwise return `null`. |
| 333 */ | 348 */ |
| 334 static Token _getContainingCommentToken(Token token, int offset) { | 349 static Token _getContainingCommentToken(Token token, int offset) { |
| 335 if (token == null) { | 350 if (token == null) { |
| 336 return null; | 351 return null; |
| 337 } | 352 } |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 413 return token.type == TokenType.KEYWORD || | 428 return token.type == TokenType.KEYWORD || |
| 414 previous.type == TokenType.IDENTIFIER; | 429 previous.type == TokenType.IDENTIFIER; |
| 415 } else { | 430 } else { |
| 416 return false; | 431 return false; |
| 417 } | 432 } |
| 418 } | 433 } |
| 419 | 434 |
| 420 /** | 435 /** |
| 421 * Return `true` if the parameter is a functional parameter. | 436 * Return `true` if the parameter is a functional parameter. |
| 422 */ | 437 */ |
| 423 static bool _isFunctionalParameter( | 438 static bool _isFunctionalParameter(List<ParameterElement> parameters, |
| 424 List<ParameterElement> parameters, int paramIndex) { | 439 int paramIndex, AstNode containingNode) { |
| 440 DartType paramType; |
| 425 if (paramIndex < parameters.length) { | 441 if (paramIndex < parameters.length) { |
| 426 ParameterElement param = parameters[paramIndex]; | 442 ParameterElement param = parameters[paramIndex]; |
| 427 DartType paramType = param.type; | |
| 428 if (param.parameterKind == ParameterKind.NAMED) { | 443 if (param.parameterKind == ParameterKind.NAMED) { |
| 429 // TODO(danrubel) handle named parameters | 444 if (containingNode is NamedExpression) { |
| 430 return false; | 445 String name = containingNode.name?.label?.name; |
| 446 param = parameters.firstWhere( |
| 447 (ParameterElement param) => |
| 448 param.parameterKind == ParameterKind.NAMED && |
| 449 param.name == name, |
| 450 orElse: () => null); |
| 451 paramType = param?.type; |
| 452 } |
| 431 } else { | 453 } else { |
| 432 return paramType is FunctionType || paramType is FunctionTypeAlias; | 454 paramType = param.type; |
| 433 } | 455 } |
| 434 } | 456 } |
| 435 return false; | 457 return paramType is FunctionType || paramType is FunctionTypeAlias; |
| 436 } | 458 } |
| 437 } | 459 } |
| OLD | NEW |