blob: cfd7cf4728cb1c088d0d7c6c99ff01f4ecf61ccd [file] [log] [blame]
// Copyright (c) 2017, 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.
/**
* A collection of utility methods used by completion contributors.
*/
import 'package:analysis_server/src/protocol_server.dart'
show CompletionSuggestion, CompletionSuggestionKind, Location;
import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/standard_ast_factory.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/ast/token.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart' as protocol
show Element, ElementKind;
/**
* The name of the type `dynamic`;
*/
const DYNAMIC = 'dynamic';
/**
* A marker used in place of `null` when a function has no return type.
*/
final TypeName NO_RETURN_TYPE = astFactory.typeName(
astFactory.simpleIdentifier(new StringToken(TokenType.IDENTIFIER, '', 0)),
null);
/**
* Add default argument list text and ranges based on the given [requiredParams]
* and [namedParams].
*/
void addDefaultArgDetails(
CompletionSuggestion suggestion,
Element element,
Iterable<ParameterElement> requiredParams,
Iterable<ParameterElement> namedParams) {
StringBuffer sb = new StringBuffer();
List<int> ranges = <int>[];
int offset;
for (ParameterElement param in requiredParams) {
if (sb.isNotEmpty) {
sb.write(', ');
}
offset = sb.length;
String name = param.name;
sb.write(name);
ranges.addAll([offset, name.length]);
}
for (ParameterElement param in namedParams) {
if (param.hasRequired) {
if (sb.isNotEmpty) {
sb.write(', ');
}
String name = param.name;
sb.write('$name: ');
offset = sb.length;
String defaultValue = _getDefaultValue(param);
sb.write(defaultValue);
ranges.addAll([offset, defaultValue.length]);
}
}
suggestion.defaultArgumentListString = sb.isNotEmpty ? sb.toString() : null;
suggestion.defaultArgumentListTextRanges = ranges.isNotEmpty ? ranges : null;
}
/**
* Create a new protocol Element for inclusion in a completion suggestion.
*/
protocol.Element createLocalElement(
Source source, protocol.ElementKind kind, SimpleIdentifier id,
{String parameters,
TypeAnnotation returnType,
bool isAbstract: false,
bool isDeprecated: false}) {
String name;
Location location;
if (id != null) {
name = id.name;
// TODO(danrubel) use lineInfo to determine startLine and startColumn
location = new Location(source.fullName, id.offset, id.length, 0, 0);
} else {
name = '';
location = new Location(source.fullName, -1, 0, 1, 0);
}
int flags = protocol.Element.makeFlags(
isAbstract: isAbstract,
isDeprecated: isDeprecated,
isPrivate: Identifier.isPrivateName(name));
return new protocol.Element(kind, name, flags,
location: location,
parameters: parameters,
returnType: nameForType(returnType));
}
/**
* Create a new suggestion for the given [fieldDecl]. Return the new suggestion
* or `null` if it could not be created.
*/
CompletionSuggestion createLocalFieldSuggestion(
Source source, FieldDeclaration fieldDecl, VariableDeclaration varDecl) {
bool deprecated = isDeprecated(fieldDecl) || isDeprecated(varDecl);
TypeAnnotation type = fieldDecl.fields.type;
return createLocalSuggestion(
varDecl.name, deprecated, DART_RELEVANCE_LOCAL_FIELD, type,
classDecl: fieldDecl.parent,
element: createLocalElement(
source, protocol.ElementKind.FIELD, varDecl.name,
returnType: type, isDeprecated: deprecated));
}
/**
* Create a new suggestion based upon the given information. Return the new
* suggestion or `null` if it could not be created.
*/
CompletionSuggestion createLocalSuggestion(SimpleIdentifier id,
bool isDeprecated, int defaultRelevance, TypeAnnotation returnType,
{ClassDeclaration classDecl,
CompletionSuggestionKind kind = CompletionSuggestionKind.INVOCATION,
protocol.Element element}) {
if (id == null) {
return null;
}
String completion = id.name;
if (completion == null || completion.length <= 0 || completion == '_') {
return null;
}
CompletionSuggestion suggestion = new CompletionSuggestion(
kind,
isDeprecated ? DART_RELEVANCE_LOW : defaultRelevance,
completion,
completion.length,
0,
isDeprecated,
false,
returnType: nameForType(returnType),
element: element);
if (classDecl != null) {
SimpleIdentifier classId = classDecl.name;
if (classId != null) {
String className = classId.name;
if (className != null && className.length > 0) {
suggestion.declaringType = className;
}
}
}
return suggestion;
}
String getDefaultStringParameterValue(ParameterElement param) {
if (param != null) {
DartType type = param.type;
if (type is InterfaceType && isDartList(type)) {
List<DartType> typeArguments = type.typeArguments;
if (typeArguments.length == 1) {
DartType typeArg = typeArguments.first;
String typeInfo = !typeArg.isDynamic ? '<${typeArg.name}>' : '';
return '$typeInfo[]';
}
}
if (type is FunctionType) {
String params = type.parameters
.map((p) => '${getTypeString(p.type)}${p.name}')
.join(', ');
//TODO(pq): consider adding a `TODO:` message in generated stub
return '($params) {}';
}
//TODO(pq): support map literals
}
return null;
}
String getTypeString(DartType type) => type.isDynamic ? '' : '${type.name} ';
bool isDartList(DartType type) {
ClassElement element = type.element;
if (element != null) {
return element.name == "List" && element.library.isDartCore;
}
return false;
}
/**
* Return `true` if the @deprecated annotation is present on the given [node].
*/
bool isDeprecated(AnnotatedNode node) {
if (node != null) {
NodeList<Annotation> metadata = node.metadata;
if (metadata != null) {
return metadata.any((Annotation a) {
return a.name is SimpleIdentifier && a.name.name == 'deprecated';
});
}
}
return false;
}
/**
* Return the name for the given [type].
*/
String nameForType(TypeAnnotation type) {
if (type == NO_RETURN_TYPE) {
return null;
}
if (type == null) {
return DYNAMIC;
}
if (type is TypeName) {
Identifier id = type.name;
if (id == null) {
return DYNAMIC;
}
String name = id.name;
if (name == null || name.length <= 0) {
return DYNAMIC;
}
TypeArgumentList typeArgs = type.typeArguments;
if (typeArgs != null) {
//TODO (danrubel) include type arguments
}
return name;
} else if (type is GenericFunctionType) {
// TODO(brianwilkerson) Implement this.
}
return DYNAMIC;
}
//TODO(pq): fix to use getDefaultStringParameterValue()
String _getDefaultValue(ParameterElement param) => 'null';