| // 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'; |
| |
| const _DYNAMIC = 'dynamic'; |
| |
| final TypeName _NO_RETURN_TYPE = new TypeName( |
| new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, '', 0)), 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 != null ? 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; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * 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; |
| |
| // Collect suggestions from the specific child [AstNode] that contains |
| // the completion offset and all of its parents recursively. |
| if (optype.includeReturnValueSuggestions || |
| optype.includeTypeNameSuggestions || |
| optype.includeVoidReturnSuggestions) { |
| _LocalVisitor localVisitor = |
| new _LocalVisitor(request, request.offset, optype); |
| localVisitor.visit(request.node); |
| } |
| if (optype.includeStatementLabelSuggestions || |
| optype.includeCaseLabelSuggestions) { |
| _LabelVisitor labelVisitor = new _LabelVisitor(request, |
| optype.includeStatementLabelSuggestions, |
| optype.includeCaseLabelSuggestions); |
| labelVisitor.visit(request.node); |
| } |
| if (optype.includeConstructorSuggestions) { |
| new _ConstructorVisitor(request).visit(request.node); |
| } |
| |
| // If target is an argument in an argument list |
| // then suggestions may need to be adjusted |
| return request.target.argIndex == null; |
| } |
| |
| @override |
| Future<bool> computeFull(DartCompletionRequest request) { |
| _updateSuggestions(request); |
| return new Future.value(false); |
| } |
| |
| /** |
| * If target is a function argument, suggest identifiers not invocations |
| */ |
| void _updateSuggestions(DartCompletionRequest request) { |
| if (request.target.isFunctionalArgument()) { |
| request.convertInvocationsToIdentifiers(); |
| } |
| } |
| } |
| |
| /** |
| * A visitor for collecting constructor suggestions. |
| */ |
| class _ConstructorVisitor extends LocalDeclarationVisitor { |
| final DartCompletionRequest request; |
| |
| _ConstructorVisitor(DartCompletionRequest request) |
| : super(request.offset), |
| request = request; |
| |
| @override |
| void declaredClass(ClassDeclaration declaration) { |
| bool found = false; |
| for (ClassMember member in declaration.members) { |
| if (member is ConstructorDeclaration) { |
| found = true; |
| _addSuggestion(declaration, member); |
| } |
| } |
| if (!found) { |
| _addSuggestion(declaration, null); |
| } |
| } |
| |
| @override |
| void declaredClassTypeAlias(ClassTypeAlias declaration) { |
| // TODO: implement declaredClassTypeAlias |
| } |
| |
| @override |
| void declaredField(FieldDeclaration fieldDecl, VariableDeclaration varDecl) { |
| // TODO: implement declaredField |
| } |
| |
| @override |
| void declaredFunction(FunctionDeclaration declaration) { |
| // TODO: implement declaredFunction |
| } |
| |
| @override |
| void declaredFunctionTypeAlias(FunctionTypeAlias declaration) { |
| // TODO: implement declaredFunctionTypeAlias |
| } |
| |
| @override |
| void declaredLabel(Label label, bool isCaseLabel) { |
| // TODO: implement declaredLabel |
| } |
| |
| @override |
| void declaredLocalVar(SimpleIdentifier name, TypeName type) { |
| // TODO: implement declaredLocalVar |
| } |
| |
| @override |
| void declaredMethod(MethodDeclaration declaration) { |
| // TODO: implement declaredMethod |
| } |
| |
| @override |
| void declaredParam(SimpleIdentifier name, TypeName type) { |
| // TODO: implement declaredParam |
| } |
| |
| @override |
| void declaredTopLevelVar( |
| VariableDeclarationList varList, VariableDeclaration varDecl) { |
| // TODO: implement declaredTopLevelVar |
| } |
| |
| /** |
| * For the given class and constructor, |
| * add a suggestion of the form B(...) or B.name(...). |
| * If the given constructor is `null` |
| * then add a default constructor suggestion. |
| */ |
| CompletionSuggestion _addSuggestion( |
| ClassDeclaration classDecl, ConstructorDeclaration constructorDecl) { |
| SimpleIdentifier elemId; |
| String completion = classDecl.name.name; |
| if (constructorDecl != null) { |
| elemId = constructorDecl.name; |
| if (elemId != null) { |
| String name = elemId.name; |
| if (name != null && name.length > 0) { |
| completion = '$completion.$name'; |
| } |
| } |
| } |
| bool isDeprecated = |
| constructorDecl != null && _isDeprecated(constructorDecl); |
| List<String> parameterNames = new List<String>(); |
| List<String> parameterTypes = new List<String>(); |
| int requiredParameterCount = 0; |
| bool hasNamedParameters = false; |
| StringBuffer paramBuf = new StringBuffer(); |
| paramBuf.write('('); |
| int paramCount = 0; |
| if (constructorDecl != null) { |
| for (FormalParameter param in constructorDecl.parameters.parameters) { |
| if (paramCount > 0) { |
| paramBuf.write(', '); |
| } |
| String paramName; |
| String typeName; |
| if (param is NormalFormalParameter) { |
| paramName = param.identifier.name; |
| typeName = _nameForParamType(param); |
| ++requiredParameterCount; |
| } else if (param is DefaultFormalParameter) { |
| NormalFormalParameter childParam = param.parameter; |
| paramName = childParam.identifier.name; |
| typeName = _nameForParamType(childParam); |
| if (param.kind == ParameterKind.NAMED) { |
| hasNamedParameters = true; |
| } |
| if (paramCount == requiredParameterCount) { |
| paramBuf.write(hasNamedParameters ? '{' : '['); |
| } |
| } |
| parameterNames.add(paramName); |
| parameterTypes.add(typeName); |
| paramBuf.write(typeName); |
| paramBuf.write(' '); |
| paramBuf.write(paramName); |
| ++paramCount; |
| } |
| } |
| if (paramCount > requiredParameterCount) { |
| paramBuf.write(hasNamedParameters ? '}' : ']'); |
| } |
| paramBuf.write(')'); |
| protocol.Element element = _createElement( |
| protocol.ElementKind.CONSTRUCTOR, elemId, |
| parameters: paramBuf.toString()); |
| element.returnType = classDecl.name.name; |
| CompletionSuggestion suggestion = new CompletionSuggestion( |
| CompletionSuggestionKind.INVOCATION, |
| isDeprecated ? DART_RELEVANCE_LOW : DART_RELEVANCE_DEFAULT, completion, |
| completion.length, 0, isDeprecated, false, |
| declaringType: classDecl.name.name, |
| element: element, |
| parameterNames: parameterNames, |
| parameterTypes: parameterTypes, |
| requiredParameterCount: requiredParameterCount, |
| hasNamedParameters: hasNamedParameters); |
| request.suggestions.add(suggestion); |
| return suggestion; |
| } |
| |
| /** |
| * Determine the name of the type for the given constructor parameter. |
| */ |
| String _nameForParamType(NormalFormalParameter param) { |
| if (param is SimpleFormalParameter) { |
| return _nameForType(param.type); |
| } |
| SimpleIdentifier id = param.identifier; |
| if (param is FieldFormalParameter && id != null) { |
| String fieldName = id.name; |
| AstNode classDecl = param.getAncestor((p) => p is ClassDeclaration); |
| if (classDecl is ClassDeclaration) { |
| for (ClassMember member in classDecl.members) { |
| if (member is FieldDeclaration) { |
| for (VariableDeclaration field in member.fields.variables) { |
| if (field.name.name == fieldName) { |
| return _nameForType(member.fields.type); |
| } |
| } |
| } |
| } |
| } |
| } |
| return _DYNAMIC; |
| } |
| } |
| |
| /** |
| * 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 |
| void visitFunctionExpression(FunctionExpression node) { |
| // Labels are only accessible within the local function, so stop visiting |
| // once we reach a function boundary. |
| finished(); |
| } |
| |
| @override |
| void visitMethodDeclaration(MethodDeclaration node) { |
| // Labels are only accessible within the local function, so stop visiting |
| // once we reach a function boundary. |
| finished(); |
| } |
| |
| CompletionSuggestion _addSuggestion(SimpleIdentifier id) { |
| if (id != null) { |
| String completion = id.name; |
| if (completion != null && completion.length > 0 && completion != '_') { |
| CompletionSuggestion suggestion = new CompletionSuggestion( |
| CompletionSuggestionKind.IDENTIFIER, DART_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 { |
| final DartCompletionRequest request; |
| final OpType optype; |
| |
| _LocalVisitor(this.request, int offset, this.optype) : super(offset); |
| |
| @override |
| void declaredClass(ClassDeclaration declaration) { |
| if (optype.includeTypeNameSuggestions) { |
| bool isDeprecated = _isDeprecated(declaration); |
| CompletionSuggestion suggestion = _addSuggestion(declaration.name, |
| _NO_RETURN_TYPE, isDeprecated, DART_RELEVANCE_DEFAULT); |
| 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) { |
| if (optype.includeTypeNameSuggestions) { |
| bool isDeprecated = _isDeprecated(declaration); |
| CompletionSuggestion suggestion = _addSuggestion(declaration.name, |
| _NO_RETURN_TYPE, isDeprecated, DART_RELEVANCE_DEFAULT); |
| 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 (optype.includeReturnValueSuggestions) { |
| bool isDeprecated = _isDeprecated(fieldDecl) || _isDeprecated(varDecl); |
| TypeName type = fieldDecl.fields.type; |
| CompletionSuggestion suggestion = _addSuggestion( |
| varDecl.name, type, isDeprecated, DART_RELEVANCE_LOCAL_FIELD, |
| classDecl: fieldDecl.parent); |
| if (suggestion != null) { |
| suggestion.element = _createElement( |
| protocol.ElementKind.FIELD, varDecl.name, |
| returnType: type, isDeprecated: isDeprecated); |
| } |
| } |
| } |
| |
| @override |
| void declaredFunction(FunctionDeclaration declaration) { |
| if (optype.includeReturnValueSuggestions || |
| optype.includeVoidReturnSuggestions) { |
| TypeName returnType = declaration.returnType; |
| bool isDeprecated = _isDeprecated(declaration); |
| protocol.ElementKind kind; |
| int defaultRelevance = DART_RELEVANCE_DEFAULT; |
| if (declaration.isGetter) { |
| kind = protocol.ElementKind.GETTER; |
| defaultRelevance = DART_RELEVANCE_LOCAL_ACCESSOR; |
| } else if (declaration.isSetter) { |
| if (!optype.includeVoidReturnSuggestions) { |
| return; |
| } |
| kind = protocol.ElementKind.SETTER; |
| returnType = _NO_RETURN_TYPE; |
| defaultRelevance = DART_RELEVANCE_LOCAL_ACCESSOR; |
| } else { |
| if (!optype.includeVoidReturnSuggestions && _isVoid(returnType)) { |
| return; |
| } |
| kind = protocol.ElementKind.FUNCTION; |
| defaultRelevance = DART_RELEVANCE_LOCAL_FUNCTION; |
| } |
| CompletionSuggestion suggestion = _addSuggestion( |
| declaration.name, returnType, isDeprecated, defaultRelevance); |
| 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) { |
| if (optype.includeTypeNameSuggestions) { |
| bool isDeprecated = _isDeprecated(declaration); |
| TypeName returnType = declaration.returnType; |
| CompletionSuggestion suggestion = _addSuggestion( |
| declaration.name, returnType, isDeprecated, DART_RELEVANCE_DEFAULT); |
| 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 (optype.includeReturnValueSuggestions) { |
| CompletionSuggestion suggestion = |
| _addSuggestion(name, type, false, DART_RELEVANCE_LOCAL_VARIABLE); |
| if (suggestion != null) { |
| suggestion.element = _createElement( |
| protocol.ElementKind.LOCAL_VARIABLE, name, returnType: type); |
| } |
| } |
| } |
| |
| @override |
| void declaredMethod(MethodDeclaration declaration) { |
| if (optype.includeReturnValueSuggestions || |
| optype.includeVoidReturnSuggestions) { |
| protocol.ElementKind kind; |
| String parameters; |
| TypeName returnType = declaration.returnType; |
| int defaultRelevance = DART_RELEVANCE_DEFAULT; |
| if (declaration.isGetter) { |
| kind = protocol.ElementKind.GETTER; |
| parameters = null; |
| defaultRelevance = DART_RELEVANCE_LOCAL_ACCESSOR; |
| } else if (declaration.isSetter) { |
| if (!optype.includeVoidReturnSuggestions) { |
| return; |
| } |
| kind = protocol.ElementKind.SETTER; |
| returnType = _NO_RETURN_TYPE; |
| defaultRelevance = DART_RELEVANCE_LOCAL_ACCESSOR; |
| } else { |
| if (!optype.includeVoidReturnSuggestions && _isVoid(returnType)) { |
| return; |
| } |
| kind = protocol.ElementKind.METHOD; |
| parameters = declaration.parameters.toSource(); |
| defaultRelevance = DART_RELEVANCE_LOCAL_METHOD; |
| } |
| bool isDeprecated = _isDeprecated(declaration); |
| CompletionSuggestion suggestion = _addSuggestion( |
| declaration.name, returnType, isDeprecated, defaultRelevance, |
| 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 (optype.includeReturnValueSuggestions) { |
| CompletionSuggestion suggestion = |
| _addSuggestion(name, type, false, DART_RELEVANCE_PARAMETER); |
| if (suggestion != null) { |
| suggestion.element = _createElement( |
| protocol.ElementKind.PARAMETER, name, returnType: type); |
| } |
| } |
| } |
| |
| @override |
| void declaredTopLevelVar( |
| VariableDeclarationList varList, VariableDeclaration varDecl) { |
| if (optype.includeReturnValueSuggestions) { |
| bool isDeprecated = _isDeprecated(varList) || _isDeprecated(varDecl); |
| CompletionSuggestion suggestion = _addSuggestion(varDecl.name, |
| varList.type, isDeprecated, DART_RELEVANCE_LOCAL_TOP_LEVEL_VARIABLE); |
| 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, int defaultRelevance, {ClassDeclaration classDecl}) { |
| if (id != null) { |
| String completion = id.name; |
| if (completion != null && completion.length > 0 && completion != '_') { |
| CompletionSuggestion suggestion = new CompletionSuggestion( |
| CompletionSuggestionKind.INVOCATION, |
| isDeprecated ? DART_RELEVANCE_LOW : defaultRelevance, 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; |
| } |
| |
| bool _isVoid(TypeName returnType) { |
| if (returnType != null) { |
| Identifier id = returnType.name; |
| if (id != null && id.name == 'void') { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |