| // 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. |
| |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/file_system/file_system.dart'; |
| import 'package:analyzer/src/generated/source.dart' show Source, UriKind; |
| import 'package:analyzer_plugin/protocol/protocol_common.dart' |
| hide Element, ElementKind; |
| import 'package:analyzer_plugin/src/utilities/documentation.dart'; |
| import 'package:analyzer_plugin/utilities/analyzer_converter.dart'; |
| import 'package:analyzer_plugin/utilities/completion/relevance.dart'; |
| import 'package:analyzer_plugin/utilities/completion/suggestion_builder.dart'; |
| |
| /** |
| * An object used to build code completion suggestions for Dart code. |
| */ |
| class SuggestionBuilderImpl implements SuggestionBuilder { |
| /** |
| * The resource provider used to access the file system. |
| */ |
| final ResourceProvider resourceProvider; |
| |
| /** |
| * The converter used to convert analyzer objects to protocol objects. |
| */ |
| final AnalyzerConverter converter = new AnalyzerConverter(); |
| |
| /** |
| * Initialize a newly created suggestion builder. |
| */ |
| SuggestionBuilderImpl(this.resourceProvider); |
| |
| /** |
| * 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) { |
| // Copied from analysis_server/lib/src/services/completion/dart/suggestion_builder.dart |
| StringBuffer buffer = new StringBuffer(); |
| List<int> ranges = <int>[]; |
| |
| int offset; |
| |
| for (ParameterElement param in requiredParams) { |
| if (buffer.isNotEmpty) { |
| buffer.write(', '); |
| } |
| offset = buffer.length; |
| String name = param.name; |
| buffer.write(name); |
| ranges.addAll([offset, name.length]); |
| } |
| |
| for (ParameterElement param in namedParams) { |
| if (param.hasRequired) { |
| if (buffer.isNotEmpty) { |
| buffer.write(', '); |
| } |
| String name = param.name; |
| buffer.write('$name: '); |
| offset = buffer.length; |
| String defaultValue = 'null'; // originally _getDefaultValue(param) |
| buffer.write(defaultValue); |
| ranges.addAll([offset, defaultValue.length]); |
| } |
| } |
| |
| suggestion.defaultArgumentListString = |
| buffer.isNotEmpty ? buffer.toString() : null; |
| suggestion.defaultArgumentListTextRanges = |
| ranges.isNotEmpty ? ranges : null; |
| } |
| |
| @override |
| CompletionSuggestion forElement(Element element, |
| {String completion, |
| CompletionSuggestionKind kind: CompletionSuggestionKind.INVOCATION, |
| int relevance: DART_RELEVANCE_DEFAULT, |
| Source importForSource}) { |
| // Copied from analysis_server/lib/src/services/completion/dart/suggestion_builder.dart |
| if (element == null) { |
| return null; |
| } |
| if (element is ExecutableElement && element.isOperator) { |
| // Do not include operators in suggestions |
| return null; |
| } |
| if (completion == null) { |
| completion = element.displayName; |
| } |
| bool isDeprecated = element.hasDeprecated; |
| CompletionSuggestion suggestion = new CompletionSuggestion( |
| kind, |
| isDeprecated ? DART_RELEVANCE_LOW : relevance, |
| completion, |
| completion.length, |
| 0, |
| isDeprecated, |
| false); |
| |
| // Attach docs. |
| String doc = removeDartDocDelimiters(element.documentationComment); |
| suggestion.docComplete = doc; |
| suggestion.docSummary = getDartDocSummary(doc); |
| |
| suggestion.element = converter.convertElement(element); |
| Element enclosingElement = element.enclosingElement; |
| if (enclosingElement is ClassElement) { |
| suggestion.declaringType = enclosingElement.displayName; |
| } |
| suggestion.returnType = getReturnTypeString(element); |
| if (element is ExecutableElement && element is! PropertyAccessorElement) { |
| suggestion.parameterNames = element.parameters |
| .map((ParameterElement parameter) => parameter.name) |
| .toList(); |
| suggestion.parameterTypes = |
| element.parameters.map((ParameterElement parameter) { |
| DartType paramType = parameter.type; |
| // Gracefully degrade if type not resolved yet |
| return paramType != null ? paramType.displayName : 'var'; |
| }).toList(); |
| |
| Iterable<ParameterElement> requiredParameters = element.parameters |
| .where((ParameterElement param) => param.isNotOptional); |
| suggestion.requiredParameterCount = requiredParameters.length; |
| |
| Iterable<ParameterElement> namedParameters = |
| element.parameters.where((ParameterElement param) => param.isNamed); |
| suggestion.hasNamedParameters = namedParameters.isNotEmpty; |
| |
| addDefaultArgDetails( |
| suggestion, element, requiredParameters, namedParameters); |
| } |
| if (importForSource != null) { |
| String srcPath = |
| resourceProvider.pathContext.dirname(importForSource.fullName); |
| LibraryElement libElem = element.library; |
| if (libElem != null) { |
| Source libSource = libElem.source; |
| if (libSource != null) { |
| UriKind uriKind = libSource.uriKind; |
| if (uriKind == UriKind.DART_URI) { |
| suggestion.importUri = libSource.uri.toString(); |
| } else if (uriKind == UriKind.PACKAGE_URI) { |
| suggestion.importUri = libSource.uri.toString(); |
| } else if (uriKind == UriKind.FILE_URI && |
| element.source.uriKind == UriKind.FILE_URI) { |
| try { |
| suggestion.importUri = resourceProvider.pathContext |
| .relative(libSource.fullName, from: srcPath); |
| } catch (_) { |
| // ignored |
| } |
| } |
| } |
| } |
| if (suggestion.importUri == null) { |
| // Do not include out of scope suggestions |
| // for which we cannot determine an import |
| return null; |
| } |
| } |
| return suggestion; |
| } |
| |
| String getReturnTypeString(Element element) { |
| // Copied from analysis_server/lib/src/protocol_server.dart |
| if (element is ExecutableElement) { |
| if (element.kind == ElementKind.SETTER) { |
| return null; |
| } else { |
| return element.returnType?.toString(); |
| } |
| } else if (element is VariableElement) { |
| DartType type = element.type; |
| return type != null ? type.displayName : 'dynamic'; |
| } else if (element is FunctionTypeAliasElement) { |
| return element.returnType.toString(); |
| } else { |
| return null; |
| } |
| } |
| } |