blob: 46e20aa5ff6fb60b59a50e42d37d9958bf4e1ef4 [file] [log] [blame]
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library services.completion.contributor.dart.arglist;
import 'dart:async';
import 'package:analysis_server/src/protocol_server.dart'
hide Element, ElementKind;
import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/element.dart';
import 'package:analyzer/src/generated/utilities_dart.dart';
/**
* Determine the number of arguments.
*/
int _argCount(DartCompletionRequest request) {
AstNode node = request.target.containingNode;
if (node is ArgumentList) {
return node.arguments.length;
}
return 0;
}
String _getParamType(ParameterElement param) {
DartType type = param.type;
if (type != null) {
return type.displayName;
}
return 'dynamic';
}
/**
* If the containing [node] is an argument list
* or named expression in an argument list
* then return the simple identifier for the method, constructor, or annotation
* to which the argument list is associated
*/
SimpleIdentifier _getTargetId(AstNode node) {
if (node is NamedExpression) {
return _getTargetId(node.parent);
}
if (node is ArgumentList) {
AstNode parent = node.parent;
if (parent is MethodInvocation) {
return parent.methodName;
}
if (parent is InstanceCreationExpression) {
ConstructorName constructorName = parent.constructorName;
if (constructorName != null) {
if (constructorName.name != null) {
return constructorName.name;
}
Identifier typeName = constructorName.type.name;
if (typeName is SimpleIdentifier) {
return typeName;
}
if (typeName is PrefixedIdentifier) {
return typeName.identifier;
}
}
}
if (parent is Annotation) {
return parent.constructorName ?? parent.name;
}
}
return null;
}
/**
* Determine if the completion target is at the end of the list of arguments.
*/
bool _isAppendingToArgList(DartCompletionRequest request) {
AstNode node = request.target.containingNode;
if (node is ArgumentList) {
var entity = request.target.entity;
if (entity == node.rightParenthesis) {
return true;
}
if (node.arguments.length > 0 && node.arguments.last == entity) {
return entity is SimpleIdentifier;
}
}
return false;
}
/**
* Determine if the completion target is an emtpy argument list.
*/
bool _isEmptyArgList(DartCompletionRequest request) {
AstNode node = request.target.containingNode;
return node is ArgumentList &&
node.leftParenthesis.next == node.rightParenthesis;
}
/**
* Return a collection of currently specified named arguments
*/
Iterable<String> _namedArgs(DartCompletionRequest request) {
AstNode node = request.target.containingNode;
List<String> namedArgs = new List<String>();
if (node is ArgumentList) {
for (Expression arg in node.arguments) {
if (arg is NamedExpression) {
namedArgs.add(arg.name.label.name);
}
}
}
return namedArgs;
}
/**
* A contributor for calculating `completion.getSuggestions` request results
* when the cursor position is inside the arguments to a method call.
*/
class ArgListContributor extends DartCompletionContributor {
DartCompletionRequest request;
List<CompletionSuggestion> suggestions;
@override
Future<List<CompletionSuggestion>> computeSuggestions(
DartCompletionRequest request) async {
this.request = request;
this.suggestions = <CompletionSuggestion>[];
// Determine if the target is in an argument list
// for a method or a constructor or an annotation
// and resolve the identifier
SimpleIdentifier targetId = _getTargetId(request.target.containingNode);
if (targetId == null) {
return EMPTY_LIST;
}
// Resolve the target expression to determine the arguments
await request.resolveIdentifier(targetId);
// Gracefully degrade if the element could not be resolved
// e.g. target changed, completion aborted
targetId = _getTargetId(request.target.containingNode);
if (targetId == null) {
return EMPTY_LIST;
}
Element elem = targetId.bestElement;
if (elem == null) {
return EMPTY_LIST;
}
// Generate argument list suggestion based upon the type of element
if (elem is ClassElement) {
for (ConstructorElement constructor in elem.constructors) {
if (!constructor.isFactory) {
_addSuggestions(constructor.parameters);
return suggestions;
}
}
}
if (elem is ConstructorElement) {
_addSuggestions(elem.parameters);
return suggestions;
}
if (elem is FunctionElement) {
_addSuggestions(elem.parameters);
return suggestions;
}
if (elem is MethodElement) {
_addSuggestions(elem.parameters);
return suggestions;
}
return EMPTY_LIST;
}
void _addArgListSuggestion(Iterable<ParameterElement> requiredParam) {
StringBuffer completion = new StringBuffer('(');
List<String> paramNames = new List<String>();
List<String> paramTypes = new List<String>();
for (ParameterElement param in requiredParam) {
String name = param.name;
if (name != null && name.length > 0) {
if (completion.length > 1) {
completion.write(', ');
}
completion.write(name);
paramNames.add(name);
paramTypes.add(_getParamType(param));
}
}
completion.write(')');
CompletionSuggestion suggestion = new CompletionSuggestion(
CompletionSuggestionKind.ARGUMENT_LIST,
DART_RELEVANCE_HIGH,
completion.toString(),
completion.length,
0,
false,
false);
suggestion.parameterNames = paramNames;
suggestion.parameterTypes = paramTypes;
suggestions.add(suggestion);
}
void _addDefaultParamSuggestions(Iterable<ParameterElement> parameters) {
Iterable<String> namedArgs = _namedArgs(request);
for (ParameterElement param in parameters) {
if (param.parameterKind == ParameterKind.NAMED) {
_addNamedParameterSuggestion(request, namedArgs, param.name);
}
}
}
void _addNamedParameterSuggestion(
DartCompletionRequest request, List<String> namedArgs, String name) {
if (name != null && name.length > 0 && !namedArgs.contains(name)) {
suggestions.add(new CompletionSuggestion(
CompletionSuggestionKind.NAMED_ARGUMENT,
DART_RELEVANCE_NAMED_PARAMETER,
'$name: ',
name.length + 2,
0,
false,
false));
}
}
void _addSuggestions(Iterable<ParameterElement> parameters) {
if (parameters == null || parameters.length == 0) {
return;
}
Iterable<ParameterElement> requiredParam = parameters.where(
(ParameterElement p) => p.parameterKind == ParameterKind.REQUIRED);
int requiredCount = requiredParam.length;
if (requiredCount > 0 && _isEmptyArgList(request)) {
_addArgListSuggestion(requiredParam);
return;
}
if (_isAppendingToArgList(request)) {
if (requiredCount == 0 || requiredCount < _argCount(request)) {
_addDefaultParamSuggestions(parameters);
}
}
}
}