blob: 0460537d294e966fa8defb47c32bf9dc50aeb26e [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.computer.dart.local;
import 'dart:async';
import 'package:analysis_server/src/protocol.dart' as protocol show Element,
ElementKind;
import 'package:analysis_server/src/protocol.dart' hide Element, ElementKind;
import 'package:analysis_server/src/services/completion/dart_completion_manager.dart';
import 'package:analysis_server/src/services/completion/local_declaration_visitor.dart';
import 'package:analysis_server/src/services/completion/optype.dart';
import 'package:analyzer/src/generated/ast.dart';
import 'package:analyzer/src/generated/scanner.dart';
import 'package:analyzer/src/generated/utilities_dart.dart';
/**
* A computer for calculating `completion.getSuggestions` request results
* for the local library in which the completion is requested.
*/
class LocalComputer extends DartCompletionComputer {
@override
bool computeFast(DartCompletionRequest request) {
OpType optype = request.optype;
if (optype.includeTopLevelSuggestions) {
_LocalVisitor localVisitor = new _LocalVisitor(
request,
request.offset,
optype.includeOnlyTypeNameSuggestions,
!optype.includeVoidReturnSuggestions);
// Collect suggestions from the specific child [AstNode] that contains
// the completion offset and all of its parents recursively.
request.node.accept(localVisitor);
}
if (optype.includeStatementLabelSuggestions ||
optype.includeCaseLabelSuggestions) {
_LabelVisitor labelVisitor = new _LabelVisitor(
request,
optype.includeStatementLabelSuggestions,
optype.includeCaseLabelSuggestions);
request.node.accept(labelVisitor);
}
// If the unit is not a part and does not reference any parts
// then work is complete
return !request.unit.directives.any(
(Directive directive) =>
directive is PartOfDirective || directive is PartDirective);
}
@override
Future<bool> computeFull(DartCompletionRequest request) {
// TODO: implement computeFull
// include results from part files that are included in the library
return new Future.value(false);
}
}
/**
* A visitor for collecting suggestions for break and continue labels.
*/
class _LabelVisitor extends LocalDeclarationVisitor {
final DartCompletionRequest request;
/**
* True if statement labels should be included as suggestions.
*/
final bool includeStatementLabels;
/**
* True if case labels should be included as suggestions.
*/
final bool includeCaseLabels;
_LabelVisitor(DartCompletionRequest request, this.includeStatementLabels,
this.includeCaseLabels)
: super(request.offset),
request = request;
@override
void declaredClass(ClassDeclaration declaration) {
// ignored
}
@override
void declaredClassTypeAlias(ClassTypeAlias declaration) {
// ignored
}
@override
void declaredField(FieldDeclaration fieldDecl, VariableDeclaration varDecl) {
// ignored
}
@override
void declaredFunction(FunctionDeclaration declaration) {
// ignored
}
@override
void declaredFunctionTypeAlias(FunctionTypeAlias declaration) {
// ignored
}
@override
void declaredLabel(Label label, bool isCaseLabel) {
if (isCaseLabel ? includeCaseLabels : includeStatementLabels) {
CompletionSuggestion suggestion = _addSuggestion(label.label);
if (suggestion != null) {
suggestion.element =
_createElement(protocol.ElementKind.LABEL, label.label);
}
}
}
@override
void declaredLocalVar(SimpleIdentifier name, TypeName type) {
// ignored
}
@override
void declaredMethod(MethodDeclaration declaration) {
// ignored
}
@override
void declaredParam(SimpleIdentifier name, TypeName type) {
// ignored
}
@override
void declaredTopLevelVar(VariableDeclarationList varList,
VariableDeclaration varDecl) {
// ignored
}
@override
bool visitFunctionExpression(FunctionExpression node) {
// Labels are only accessible within the local function, so stop visiting
// once we reach a function boundary.
finished = true;
return true;
}
@override
bool visitMethodDeclaration(MethodDeclaration node) {
// Labels are only accessible within the local function, so stop visiting
// once we reach a function boundary.
finished = true;
return true;
}
CompletionSuggestion _addSuggestion(SimpleIdentifier id) {
if (id != null) {
String completion = id.name;
if (completion != null && completion.length > 0 && completion != '_') {
CompletionSuggestion suggestion = new CompletionSuggestion(
CompletionSuggestionKind.IDENTIFIER,
COMPLETION_RELEVANCE_DEFAULT,
completion,
completion.length,
0,
false,
false);
request.suggestions.add(suggestion);
return suggestion;
}
}
return null;
}
/**
* Create a new protocol Element for inclusion in a completion suggestion.
*/
protocol.Element _createElement(protocol.ElementKind kind,
SimpleIdentifier id) {
String name = id.name;
int flags =
protocol.Element.makeFlags(isPrivate: Identifier.isPrivateName(name));
return new protocol.Element(kind, name, flags);
}
}
/**
* A visitor for collecting suggestions from the most specific child [AstNode]
* that contains the completion offset to the [CompilationUnit].
*/
class _LocalVisitor extends LocalDeclarationVisitor {
static const DYNAMIC = 'dynamic';
static final TypeName NO_RETURN_TYPE = new TypeName(
new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, '', 0)),
null);
final DartCompletionRequest request;
final bool typesOnly;
final bool excludeVoidReturn;
_LocalVisitor(this.request, int offset, this.typesOnly,
this.excludeVoidReturn)
: super(offset);
@override
void declaredClass(ClassDeclaration declaration) {
bool isDeprecated = _isDeprecated(declaration);
CompletionSuggestion suggestion =
_addSuggestion(declaration.name, NO_RETURN_TYPE, isDeprecated);
if (suggestion != null) {
suggestion.element = _createElement(
protocol.ElementKind.CLASS,
declaration.name,
returnType: NO_RETURN_TYPE,
isAbstract: declaration.isAbstract,
isDeprecated: isDeprecated);
}
}
@override
void declaredClassTypeAlias(ClassTypeAlias declaration) {
bool isDeprecated = _isDeprecated(declaration);
CompletionSuggestion suggestion =
_addSuggestion(declaration.name, NO_RETURN_TYPE, isDeprecated);
if (suggestion != null) {
suggestion.element = _createElement(
protocol.ElementKind.CLASS_TYPE_ALIAS,
declaration.name,
returnType: NO_RETURN_TYPE,
isAbstract: true,
isDeprecated: isDeprecated);
}
}
@override
void declaredField(FieldDeclaration fieldDecl, VariableDeclaration varDecl) {
if (typesOnly) {
return;
}
bool isDeprecated = _isDeprecated(fieldDecl) || _isDeprecated(varDecl);
TypeName type = fieldDecl.fields.type;
CompletionSuggestion suggestion =
_addSuggestion(varDecl.name, type, isDeprecated, classDecl: fieldDecl.parent);
if (suggestion != null) {
suggestion.element = _createElement(
protocol.ElementKind.FIELD,
varDecl.name,
returnType: type,
isDeprecated: isDeprecated);
}
}
@override
void declaredFunction(FunctionDeclaration declaration) {
if (typesOnly) {
return;
}
TypeName returnType = declaration.returnType;
bool isDeprecated = _isDeprecated(declaration);
protocol.ElementKind kind;
if (declaration.isGetter) {
kind = protocol.ElementKind.GETTER;
} else if (declaration.isSetter) {
if (excludeVoidReturn) {
return;
}
kind = protocol.ElementKind.SETTER;
returnType = NO_RETURN_TYPE;
} else {
if (excludeVoidReturn && _isVoid(returnType)) {
return;
}
kind = protocol.ElementKind.FUNCTION;
}
CompletionSuggestion suggestion =
_addSuggestion(declaration.name, returnType, isDeprecated);
if (suggestion != null) {
FormalParameterList param = declaration.functionExpression.parameters;
suggestion.element = _createElement(
kind,
declaration.name,
parameters: param != null ? param.toSource() : null,
returnType: returnType,
isDeprecated: isDeprecated);
if (kind == protocol.ElementKind.FUNCTION) {
_addParameterInfo(
suggestion,
declaration.functionExpression.parameters);
}
}
}
@override
void declaredFunctionTypeAlias(FunctionTypeAlias declaration) {
bool isDeprecated = _isDeprecated(declaration);
TypeName returnType = declaration.returnType;
CompletionSuggestion suggestion =
_addSuggestion(declaration.name, returnType, isDeprecated);
if (suggestion != null) {
// TODO (danrubel) determine parameters and return type
suggestion.element = _createElement(
protocol.ElementKind.FUNCTION_TYPE_ALIAS,
declaration.name,
returnType: returnType,
isAbstract: true,
isDeprecated: isDeprecated);
}
}
@override
void declaredLabel(Label label, bool isCaseLabel) {
// ignored
}
@override
void declaredLocalVar(SimpleIdentifier name, TypeName type) {
if (typesOnly) {
return;
}
CompletionSuggestion suggestion = _addSuggestion(name, type, false);
if (suggestion != null) {
suggestion.element =
_createElement(protocol.ElementKind.LOCAL_VARIABLE, name, returnType: type);
}
}
@override
void declaredMethod(MethodDeclaration declaration) {
if (typesOnly) {
return;
}
protocol.ElementKind kind;
String parameters;
TypeName returnType = declaration.returnType;
if (declaration.isGetter) {
kind = protocol.ElementKind.GETTER;
parameters = null;
} else if (declaration.isSetter) {
if (excludeVoidReturn) {
return;
}
kind = protocol.ElementKind.SETTER;
returnType = NO_RETURN_TYPE;
} else {
if (excludeVoidReturn && _isVoid(returnType)) {
return;
}
kind = protocol.ElementKind.METHOD;
parameters = declaration.parameters.toSource();
}
bool isDeprecated = _isDeprecated(declaration);
CompletionSuggestion suggestion = _addSuggestion(
declaration.name,
returnType,
isDeprecated,
classDecl: declaration.parent);
if (suggestion != null) {
suggestion.element = _createElement(
kind,
declaration.name,
parameters: parameters,
returnType: returnType,
isAbstract: declaration.isAbstract,
isDeprecated: isDeprecated);
if (kind == protocol.ElementKind.METHOD) {
_addParameterInfo(suggestion, declaration.parameters);
}
}
}
@override
void declaredParam(SimpleIdentifier name, TypeName type) {
if (typesOnly) {
return;
}
CompletionSuggestion suggestion = _addSuggestion(name, type, false);
if (suggestion != null) {
suggestion.element =
_createElement(protocol.ElementKind.PARAMETER, name, returnType: type);
}
}
@override
void declaredTopLevelVar(VariableDeclarationList varList,
VariableDeclaration varDecl) {
if (typesOnly) {
return;
}
bool isDeprecated = _isDeprecated(varList) || _isDeprecated(varDecl);
CompletionSuggestion suggestion =
_addSuggestion(varDecl.name, varList.type, isDeprecated);
if (suggestion != null) {
suggestion.element = _createElement(
protocol.ElementKind.TOP_LEVEL_VARIABLE,
varDecl.name,
returnType: varList.type,
isDeprecated: isDeprecated);
}
}
void _addParameterInfo(CompletionSuggestion suggestion,
FormalParameterList parameters) {
var paramList = parameters.parameters;
suggestion.parameterNames =
paramList.map((FormalParameter param) => param.identifier.name).toList();
suggestion.parameterTypes = paramList.map((FormalParameter param) {
TypeName type = null;
if (param is DefaultFormalParameter) {
NormalFormalParameter child = param.parameter;
if (child is SimpleFormalParameter) {
type = child.type;
} else if (child is FieldFormalParameter) {
type = child.type;
}
}
if (param is SimpleFormalParameter) {
type = param.type;
} else if (param is FieldFormalParameter) {
type = param.type;
}
if (type == null) {
return 'dynamic';
}
Identifier typeId = type.name;
if (typeId == null) {
return 'dynamic';
}
return typeId.name;
}).toList();
suggestion.requiredParameterCount = paramList.where(
(FormalParameter param) => param is! DefaultFormalParameter).length;
suggestion.hasNamedParameters =
paramList.any((FormalParameter param) => param.kind == ParameterKind.NAMED);
}
CompletionSuggestion _addSuggestion(SimpleIdentifier id, TypeName returnType,
bool isDeprecated, {ClassDeclaration classDecl}) {
if (id != null) {
String completion = id.name;
if (completion != null && completion.length > 0 && completion != '_') {
CompletionSuggestion suggestion = new CompletionSuggestion(
CompletionSuggestionKind.INVOCATION,
isDeprecated ? COMPLETION_RELEVANCE_LOW : COMPLETION_RELEVANCE_DEFAULT,
completion,
completion.length,
0,
isDeprecated,
false,
returnType: _nameForType(returnType));
if (classDecl != null) {
SimpleIdentifier identifier = classDecl.name;
if (identifier != null) {
String name = identifier.name;
if (name != null && name.length > 0) {
suggestion.declaringType = name;
}
}
}
request.suggestions.add(suggestion);
return suggestion;
}
}
return null;
}
/**
* Create a new protocol Element for inclusion in a completion suggestion.
*/
protocol.Element _createElement(protocol.ElementKind kind,
SimpleIdentifier id, {String parameters, TypeName returnType, bool isAbstract:
false, bool isDeprecated: false}) {
String name = id.name;
int flags = protocol.Element.makeFlags(
isAbstract: isAbstract,
isDeprecated: isDeprecated,
isPrivate: Identifier.isPrivateName(name));
return new protocol.Element(
kind,
name,
flags,
parameters: parameters,
returnType: _nameForType(returnType));
}
/**
* Return `true` if the @deprecated annotation is present
*/
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;
}
bool _isVoid(TypeName returnType) {
if (returnType != null) {
Identifier id = returnType.name;
if (id != null && id.name == 'void') {
return true;
}
}
return false;
}
/**
* Return the name for the given type.
*/
String _nameForType(TypeName type) {
if (type == NO_RETURN_TYPE) {
return null;
}
if (type == null) {
return DYNAMIC;
}
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;
}
}