| // Copyright (c) 2023, 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. |
| |
| /// @docImport 'package:analyzer/src/utilities/completion_matcher.dart'; |
| library; |
| |
| import 'package:analysis_server/src/lsp/completion_utils.dart'; |
| import 'package:analysis_server/src/protocol_server.dart' |
| show CompletionSuggestionKind; |
| import 'package:analysis_server/src/services/completion/dart/feature_computer.dart'; |
| import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart'; |
| import 'package:analysis_server/src/services/completion/dart/utilities.dart'; |
| import 'package:analyzer/dart/ast/ast.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/source/source_range.dart'; |
| |
| /// Information about a code completion suggestion that might or might not be |
| /// sent to the client (that is, one that is a candidate for being sent). |
| /// |
| /// The candidate contains the information needed to |
| /// - determine whether the suggestion should be sent to the client, and |
| /// - to create the suggestion if it is to be sent. |
| /// |
| /// A [SuggestionBuilder] will be used to convert a candidate into a concrete |
| /// suggestion based on the wire protocol being used. |
| sealed class CandidateSuggestion { |
| /// The score computed by a [CompletionMatcher] for this suggestion. |
| final double matcherScore; |
| |
| /// The relevance score for this suggestion. |
| /// |
| /// The relevance score isn't computed until after the list of candidate |
| /// suggestions has been completed. |
| int relevanceScore = -1; |
| |
| CandidateSuggestion({required this.matcherScore}) : assert(matcherScore >= 0); |
| |
| /// The text to be inserted by the completion suggestion. |
| String get completion; |
| |
| @override |
| String toString() { |
| return completion; |
| } |
| } |
| |
| /// The information about a candidate suggestion based on a class. |
| final class ClassSuggestion extends ImportableSuggestion |
| implements ElementBasedSuggestion { |
| @override |
| final ClassElement element; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [element]. |
| ClassSuggestion({ |
| required super.importData, |
| required this.element, |
| required super.matcherScore, |
| }); |
| |
| @override |
| String get completion => '$completionPrefix${element.name}'; |
| } |
| |
| /// The information about a candidate suggestion based on a constructor. |
| final class ClosureSuggestion extends CandidateSuggestion with SuggestionData { |
| /// The type that the closure must conform to. |
| final FunctionType functionType; |
| |
| /// Whether a trailing comma should be included in the suggestion. |
| final bool includeTrailingComma; |
| |
| /// Whether the code for the closure is a block or an expression. |
| final bool useBlockStatement; |
| |
| /// Whether types should be specified whenever possible. |
| final bool includeTypes; |
| |
| /// The identation to be used for a multi-line completion. |
| final String indent; |
| |
| /// Initialize a newly created candidate suggestion to suggest a closure that |
| /// conforms to the given [functionType]. |
| /// |
| /// If [includeTrailingComma] is `true`, then the replacement will include a |
| /// trailing comma. |
| ClosureSuggestion({ |
| required this.functionType, |
| required this.includeTrailingComma, |
| required super.matcherScore, |
| required this.includeTypes, |
| required this.indent, |
| this.useBlockStatement = true, |
| }); |
| |
| @override |
| String get completion { |
| _init(); |
| return _data!.completion; |
| } |
| |
| @override |
| void _init() { |
| if (_data != null) { |
| return; |
| } |
| var parametersString = buildClosureParameters( |
| functionType, |
| includeTypes: includeTypes, |
| includeKeywords: true, |
| ); |
| // Build a short version of the parameter string without keywords or types |
| // for the completion label because they're less useful there and may push |
| // the end of the completion (`=>` vs `() {}`) off the end. |
| var parametersDisplayString = buildClosureParameters( |
| functionType, |
| includeKeywords: false, |
| includeTypes: false, |
| ); |
| |
| var stringBuffer = StringBuffer(parametersString); |
| String displayText; |
| int selectionOffset; |
| if (useBlockStatement) { |
| displayText = '$parametersDisplayString {}'; |
| stringBuffer.writeln(' {'); |
| stringBuffer.write('$indent '); |
| selectionOffset = stringBuffer.length; |
| stringBuffer.writeln(); |
| stringBuffer.write('$indent}'); |
| } else { |
| displayText = '$parametersDisplayString =>'; |
| stringBuffer.write(' => '); |
| selectionOffset = stringBuffer.length; |
| } |
| if (includeTrailingComma) { |
| stringBuffer.write(','); |
| } |
| var completion = stringBuffer.toString(); |
| _data = _Data(completion, selectionOffset, displayText: displayText); |
| } |
| } |
| |
| /// The information about a candidate suggestion based on a constructor. |
| final class ConstructorSuggestion extends ExecutableSuggestion |
| implements ElementBasedSuggestion { |
| @override |
| final ConstructorElement element; |
| |
| /// Whether the class name is already, implicitly or explicitly, at the call |
| /// site. That is, whether we are completing after a period. |
| final bool hasClassName; |
| |
| /// Whether a tear-off should be suggested, not an invocation. |
| /// Mutually exclusive with [isRedirect]. |
| final bool isTearOff; |
| |
| /// Whether the unnamed constructor should be suggested. |
| final bool suggestUnnamedAsNew; |
| |
| /// Whether a redirect should be suggested, not an invocation. |
| /// Mutually exclusive with [isTearOff]. |
| /// |
| /// When `true`, the unnamed constructor reference is `ClassName`. |
| /// OTOH, if [isTearOff] is `true`, we get `ClassName.new`. |
| final bool isRedirect; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [element]. |
| ConstructorSuggestion({ |
| required super.importData, |
| required this.element, |
| required this.hasClassName, |
| required this.isTearOff, |
| required this.isRedirect, |
| required this.suggestUnnamedAsNew, |
| required super.matcherScore, |
| }) : assert((isTearOff ? 1 : 0) | (isRedirect ? 1 : 0) < 2), |
| super( |
| kind: |
| isTearOff || isRedirect |
| ? CompletionSuggestionKind.IDENTIFIER |
| : CompletionSuggestionKind.INVOCATION, |
| ); |
| |
| @override |
| String get completion { |
| var enclosingClass = element.enclosingElement; |
| |
| var className = enclosingClass.displayName; |
| |
| // TODO(scheglov): Wrong, if no name, should be no completion. |
| var completion = element.name ?? ''; |
| if (suggestUnnamedAsNew) { |
| if (completion.isEmpty) { |
| completion = 'new'; |
| } |
| } else { |
| if (completion == 'new') { |
| completion = ''; |
| } |
| } |
| if (!hasClassName) { |
| if (completion.isEmpty) { |
| completion = className; |
| } else { |
| completion = '$className.$completion'; |
| } |
| } |
| return completion; |
| } |
| } |
| |
| abstract interface class ElementBasedSuggestion { |
| /// The element on which the suggestion is based. |
| Element get element; |
| } |
| |
| /// The information about a candidate suggestion based on a static field in a |
| /// location where the name of the field must be qualified by the name of the |
| /// enclosing element. |
| final class EnumConstantSuggestion extends ImportableSuggestion |
| implements ElementBasedSuggestion { |
| @override |
| final FieldElement element; |
| |
| /// Whether the name of the enum should be included in the completion. |
| final bool includeEnumName; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [element]. |
| EnumConstantSuggestion({ |
| required super.importData, |
| required this.element, |
| this.includeEnumName = true, |
| required super.matcherScore, |
| }); |
| |
| @override |
| String get completion { |
| if (includeEnumName) { |
| var enclosingElement = element.enclosingElement; |
| return '$completionPrefix${enclosingElement.displayName}.${element.displayName}'; |
| } else { |
| return element.displayName; |
| } |
| } |
| } |
| |
| /// The information about a candidate suggestion based on an enum. |
| final class EnumSuggestion extends ImportableSuggestion |
| implements ElementBasedSuggestion { |
| @override |
| final EnumElement element; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [element]. |
| EnumSuggestion({ |
| required super.importData, |
| required this.element, |
| required super.matcherScore, |
| }); |
| |
| @override |
| String get completion => '$completionPrefix${element.displayName}'; |
| } |
| |
| /// The information about a candidate suggestion based on an executable element, |
| /// either a method or function. |
| sealed class ExecutableSuggestion extends ImportableSuggestion { |
| /// The kind of suggestion to be made, either |
| /// [CompletionSuggestionKind.IDENTIFIER] or |
| /// [CompletionSuggestionKind.INVOCATION]. |
| final CompletionSuggestionKind kind; |
| |
| /// Initialize a newly created suggestion to use the given [kind] of |
| /// suggestion. |
| ExecutableSuggestion({ |
| required super.importData, |
| required this.kind, |
| required super.matcherScore, |
| }) : assert( |
| kind == CompletionSuggestionKind.IDENTIFIER || |
| kind == CompletionSuggestionKind.INVOCATION, |
| ); |
| } |
| |
| /// The information about a candidate suggestion based on an extension. |
| final class ExtensionSuggestion extends ExecutableSuggestion |
| implements ElementBasedSuggestion { |
| @override |
| final ExtensionElement element; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [element]. |
| ExtensionSuggestion({ |
| required super.importData, |
| required this.element, |
| required super.matcherScore, |
| super.kind = CompletionSuggestionKind.INVOCATION, |
| }); |
| |
| @override |
| String get completion => '$completionPrefix${element.displayName}'; |
| } |
| |
| /// The information about a candidate suggestion based on an extension type. |
| final class ExtensionTypeSuggestion extends ImportableSuggestion |
| implements ElementBasedSuggestion { |
| @override |
| final ExtensionTypeElement element; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [element]. |
| ExtensionTypeSuggestion({ |
| required super.importData, |
| required this.element, |
| required super.matcherScore, |
| }); |
| |
| @override |
| String get completion => '$completionPrefix${element.displayName}'; |
| } |
| |
| /// The information about a candidate suggestion based on a field. |
| final class FieldSuggestion extends TypedSuggestion with MemberSuggestion { |
| @override |
| final FieldElement element; |
| |
| /// The element defined by the declaration in which the suggestion is to be |
| /// applied, or `null` if the completion is in a static context. |
| @override |
| final InterfaceElement? referencingInterface; |
| |
| /// Indicates the context, whether the completion is in the body of the |
| /// declaration. |
| final bool isInDeclaration; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [element]. |
| FieldSuggestion({ |
| required this.element, |
| required this.referencingInterface, |
| required this.isInDeclaration, |
| required super.matcherScore, |
| required super.replacementRange, |
| super.addTypeAnnotation, |
| super.keyword, |
| }); |
| |
| @override |
| String get baseCompletion { |
| if (element.isEnumConstant) { |
| var constantName = element.name; |
| if (isInDeclaration) { |
| return '$constantName'; |
| } |
| var enumName = element.enclosingElement.displayName; |
| return '$enumName.$constantName'; |
| } |
| return element.displayName; |
| } |
| |
| @override |
| DartType get type => element.type; |
| } |
| |
| /// The information about a candidate suggestion based on a formal parameter. |
| final class FormalParameterSuggestion extends CandidateSuggestion |
| implements ElementBasedSuggestion { |
| @override |
| final FormalParameterElement element; |
| |
| /// The number of local variable declarations between the completion location |
| /// and [element]. |
| final int distance; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [element]. |
| FormalParameterSuggestion({ |
| required this.element, |
| required this.distance, |
| required super.matcherScore, |
| }); |
| |
| @override |
| String get completion => element.displayName; |
| } |
| |
| /// The information about a candidate suggestion based on the method `call` |
| /// defined on the class `Function`. |
| final class FunctionCall extends CandidateSuggestion { |
| /// Initialize a newly created candidate suggestion to suggest the method |
| /// `call` defined on the class `Function`. |
| FunctionCall({required super.matcherScore}); |
| |
| @override |
| String get completion => 'call()'; |
| } |
| |
| /// The information about a candidate suggestion based on a getter. |
| final class GetterSuggestion extends TypedImportableSuggestion |
| with MemberSuggestion { |
| @override |
| final GetterElement element; |
| |
| /// The element defined by the declaration in which the suggestion is to be |
| /// applied, or `null` if the completion is in a static context. |
| @override |
| final InterfaceElement? referencingInterface; |
| |
| /// Whether the accessor is being invoked with a target. |
| final bool withEnclosingName; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [element]. |
| GetterSuggestion({ |
| required this.element, |
| required this.referencingInterface, |
| required super.importData, |
| required super.matcherScore, |
| required super.replacementRange, |
| this.withEnclosingName = false, |
| super.addTypeAnnotation, |
| super.keyword, |
| }); |
| |
| @override |
| String get baseCompletion { |
| var prefix = _enclosingPrefix; |
| if (prefix.isNotEmpty) { |
| return '$prefix${element.displayName}'; |
| } |
| return element.displayName; |
| } |
| |
| @override |
| FunctionType get type => element.type; |
| |
| /// Return the name of the enclosing class or extension. |
| /// |
| /// The enclosing element must be either a class, or extension; otherwise |
| /// we either fail with assertion, or return `null`. |
| String? get _enclosingClassOrExtensionName { |
| var enclosing = element.enclosingElement; |
| if (enclosing is InterfaceElement) { |
| return enclosing.displayName; |
| } else if (enclosing is ExtensionElement) { |
| return enclosing.displayName; |
| } else { |
| assert(false, 'Expected ClassElement or ExtensionElement'); |
| return null; |
| } |
| } |
| |
| String get _enclosingPrefix { |
| if (withEnclosingName) { |
| var enclosingName = _enclosingClassOrExtensionName; |
| return enclosingName != null ? '$enclosingName.' : ''; |
| } |
| return ''; |
| } |
| } |
| |
| /// The information about a candidate suggestion based on an identifier being |
| /// guessed for a declaration site. |
| final class IdentifierSuggestion extends CandidateSuggestion { |
| /// The identifier to be inserted. |
| final String identifier; |
| |
| /// Whether an empty body should be included in the completion string. |
| final bool includeBody; |
| |
| /// Initialize a newly created candidate suggestion to suggest the |
| /// [identifier]. |
| /// |
| /// If [includeBody] is `true`, then empty curly braces will be included in |
| /// the suggestion. |
| IdentifierSuggestion({ |
| required this.identifier, |
| required this.includeBody, |
| required super.matcherScore, |
| }); |
| |
| @override |
| String get completion => identifier + (includeBody ? ' {}' : ''); |
| |
| /// The offset, from the beginning of the inserted text, where the cursor |
| /// should be positioned. |
| int get selectionOffset => identifier.length + (includeBody ? 2 : 0); |
| } |
| |
| /// The information about a candidate suggestion based on a declaration that can |
| /// be imported, or a static member of such a declaration. |
| sealed class ImportableSuggestion extends CandidateSuggestion { |
| /// Information about the import used to make this suggestion visible. |
| final ImportData? importData; |
| |
| ImportableSuggestion({required this.importData, required super.matcherScore}); |
| |
| /// The text to add before the name of the element when it is being imported |
| /// using an import prefix. |
| String get completionPrefix { |
| var prefixName = prefix; |
| return prefixName == null ? '' : '$prefixName.'; |
| } |
| |
| /// Whether this is suggesing an element that is not yet imported into the |
| /// library in which completion was requested. |
| bool get isNotImported => importData?.isNotImported ?? false; |
| |
| /// The prefix to be used in order to access the element. |
| String? get prefix => importData?.prefix; |
| } |
| |
| /// Data representing an import of a library. |
| final class ImportData { |
| /// Whether the library needs to be imported in order for the suggestion to be |
| /// accessible. |
| /// |
| /// This will return `false` when the library is already imported but the |
| /// import needs to be updated, such as by adding the element to a `show` |
| /// clause. |
| final bool isNotImported; |
| |
| /// The URI of the library from which the suggested element would be imported. |
| final Uri libraryUri; |
| |
| /// The prefix to be used in order to access the element, or `null` if no |
| /// prefix is required. |
| final String? prefix; |
| |
| /// Initialize data representing an import of a library, using the |
| /// [libraryUri], with the [prefix]. |
| ImportData({ |
| required this.libraryUri, |
| required this.prefix, |
| required this.isNotImported, |
| }); |
| } |
| |
| /// A suggestion based on an import prefix. |
| final class ImportPrefixSuggestion extends CandidateSuggestion |
| implements ElementBasedSuggestion { |
| final LibraryElement libraryElement; |
| |
| final PrefixElement prefixElement; |
| |
| ImportPrefixSuggestion({ |
| required this.libraryElement, |
| required this.prefixElement, |
| required super.matcherScore, |
| }); |
| |
| @override |
| String get completion => prefixElement.displayName; |
| |
| @override |
| Element get element => prefixElement; |
| } |
| |
| /// The information about a candidate suggestion based on a keyword. |
| final class KeywordSuggestion extends CandidateSuggestion { |
| /// The text to be inserted. |
| @override |
| final String completion; |
| |
| /// The offset, from the beginning of the inserted text, where the cursor |
| /// should be positioned. |
| final int selectionOffset; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [keyword]. |
| /// |
| /// If [annotatedText] is provided. The annotated text is used in cases where |
| /// there is boilerplate that always follows the keyword that should also be |
| /// suggested. |
| /// |
| /// If the annotated text contains a caret (`^`), then the completion will use |
| /// the annotated text with the caret removed and the index of the caret will |
| /// be used as the selection offset. If the text doesn't contain a caret, then |
| /// the insert text will be the annotated text and the selection offset will |
| /// be at the end of the text. |
| factory KeywordSuggestion.fromKeyword({ |
| required Keyword keyword, |
| required String? annotatedText, |
| required double matcherScore, |
| }) { |
| var completion = keyword.lexeme; |
| var selectionOffset = completion.length; |
| |
| if (annotatedText != null) { |
| var (rawText, caretIndex) = annotatedText.withoutCaret; |
| completion += rawText; |
| selectionOffset += caretIndex ?? rawText.length; |
| } |
| |
| return KeywordSuggestion._( |
| completion: completion, |
| selectionOffset: selectionOffset, |
| matcherScore: matcherScore, |
| ); |
| } |
| |
| /// If [annotatedText] contains a caret (`^`), then the completion will use |
| /// the annotated text with the caret removed and the index of the caret will |
| /// be used as the selection offset. If the text doesn't contain a caret, then |
| /// the insert text will be the annotated text and the selection offset will |
| /// be at the end of the text. |
| factory KeywordSuggestion.fromText( |
| String annotatedText, { |
| required double matcherScore, |
| }) { |
| var (rawText, caretIndex) = annotatedText.withoutCaret; |
| return KeywordSuggestion._( |
| completion: rawText, |
| selectionOffset: caretIndex ?? rawText.length, |
| matcherScore: matcherScore, |
| ); |
| } |
| |
| /// Initialize a newly created candidate suggestion to suggest a keyword. |
| KeywordSuggestion._({ |
| required this.completion, |
| required this.selectionOffset, |
| required super.matcherScore, |
| }); |
| } |
| |
| /// The information about a candidate suggestion based on a label. |
| final class LabelSuggestion extends CandidateSuggestion { |
| /// The label on which the suggestion is based. |
| final Label label; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [label]. |
| LabelSuggestion({required this.label, required super.matcherScore}); |
| |
| @override |
| String get completion => label.label.name; |
| } |
| |
| /// The suggestion for `loadLibrary()`. |
| final class LoadLibraryFunctionSuggestion extends ExecutableSuggestion |
| implements ElementBasedSuggestion { |
| @override |
| final TopLevelFunctionElement element; |
| |
| LoadLibraryFunctionSuggestion({ |
| required super.kind, |
| required this.element, |
| required super.matcherScore, |
| }) : super(importData: null); |
| |
| @override |
| String get completion => element.displayName; |
| } |
| |
| /// The information about a candidate suggestion based on a local function. |
| final class LocalFunctionSuggestion extends ExecutableSuggestion |
| implements ElementBasedSuggestion { |
| @override |
| final LocalFunctionElement element; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [element]. |
| LocalFunctionSuggestion({ |
| required super.kind, |
| required this.element, |
| required super.matcherScore, |
| }) : super(importData: null); |
| |
| @override |
| String get completion => element.displayName; |
| } |
| |
| /// The information about a candidate suggestion based on a local variable. |
| final class LocalVariableSuggestion extends CandidateSuggestion |
| implements ElementBasedSuggestion { |
| @override |
| final LocalVariableElement element; |
| |
| /// The number of local variables between the completion location and the |
| /// declaration of this variable. |
| final int distance; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [element]. |
| LocalVariableSuggestion({ |
| required this.element, |
| required this.distance, |
| required super.matcherScore, |
| }); |
| |
| @override |
| String get completion => element.displayName; |
| } |
| |
| /// Behavior common to suggestions that are for members of a class, enum, mixin, |
| /// etc. |
| mixin MemberSuggestion implements ElementBasedSuggestion { |
| /// The element defined by the declaration in which the suggestion is to be |
| /// applied, or `null` if the completion is in a static context. |
| InterfaceElement? get referencingInterface; |
| |
| /// Returns the value of the inheritance distance feature. |
| /// |
| /// Uses the [featureComputer] to compute the value. |
| double inheritanceDistance(FeatureComputer featureComputer) { |
| var inheritanceDistance = 0.0; |
| var element = this.element; |
| if (!(element is FieldElement && element.isEnumConstant)) { |
| var declaringClass = element.enclosingElement; |
| var referencingInterface = this.referencingInterface; |
| if (referencingInterface != null && declaringClass is InterfaceElement) { |
| inheritanceDistance = featureComputer.inheritanceDistanceFeature( |
| referencingInterface, |
| declaringClass, |
| ); |
| } |
| } |
| return inheritanceDistance; |
| } |
| } |
| |
| /// The information about a candidate suggestion based on a method. |
| final class MethodSuggestion extends TypedExecutableSuggestion |
| with MemberSuggestion { |
| @override |
| final MethodElement element; |
| |
| /// The element defined by the declaration in which the suggestion is to be |
| /// applied, or `null` if the completion is in a static context. |
| @override |
| final InterfaceElement? referencingInterface; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [element]. |
| MethodSuggestion({ |
| required super.kind, |
| required this.element, |
| required this.referencingInterface, |
| required super.importData, |
| required super.matcherScore, |
| required super.replacementRange, |
| super.addTypeAnnotation, |
| super.keyword, |
| }); |
| |
| @override |
| String get baseCompletion => element.displayName; |
| |
| @override |
| FunctionType get type => element.type; |
| } |
| |
| /// The information about a candidate suggestion based on a mixin. |
| final class MixinSuggestion extends ImportableSuggestion |
| implements ElementBasedSuggestion { |
| @override |
| final MixinElement element; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [element]. |
| MixinSuggestion({ |
| required super.importData, |
| required this.element, |
| required super.matcherScore, |
| }); |
| |
| @override |
| String get completion => '$completionPrefix${element.displayName}'; |
| } |
| |
| /// Suggest the name of a named parameter in the argument list of an invocation. |
| final class NamedArgumentSuggestion extends CandidateSuggestion |
| with SuggestionData { |
| /// The parameter whose name is to be suggested. |
| final FormalParameterElement parameter; |
| |
| /// Whether a colon should be appended after the name. |
| final bool appendColon; |
| |
| /// Whether a comma should be appended after the suggestion. |
| final bool appendComma; |
| |
| /// The number of characters that should be replaced, or `null` if the default |
| /// doesn't need to be overridden. |
| final int? replacementLength; |
| |
| final bool isWidget; |
| |
| String preferredQuoteForStrings; |
| |
| NamedArgumentSuggestion({ |
| required this.parameter, |
| required this.appendColon, |
| required this.appendComma, |
| this.replacementLength, |
| required super.matcherScore, |
| required this.preferredQuoteForStrings, |
| this.isWidget = false, |
| }); |
| |
| @override |
| String get completion { |
| _init(); |
| return _data!.completion; |
| } |
| |
| @override |
| String get displayText => completion; |
| |
| @override |
| void _init() { |
| if (_data != null) { |
| return; |
| } |
| var completion = parameter.displayName; |
| if (appendColon) { |
| completion += ': '; |
| } |
| var selectionOffset = completion.length; |
| |
| // Optionally add Flutter child widget details. |
| // TODO(pq): revisit this special casing; likely it can be generalized away. |
| if (isWidget && appendColon) { |
| var defaultValue = getDefaultStringParameterValue( |
| parameter, |
| preferredQuoteForStrings, |
| ); |
| // TODO(devoncarew): Should we remove the check here? We would then |
| // suggest values for param types like closures. |
| if (defaultValue != null && defaultValue.text == '[]') { |
| var completionLength = completion.length; |
| completion += defaultValue.text; |
| var cursorPosition = defaultValue.cursorPosition; |
| if (cursorPosition != null) { |
| selectionOffset = completionLength + cursorPosition; |
| } |
| } |
| } |
| if (appendComma) { |
| completion += ','; |
| } |
| _data = _Data(completion, selectionOffset); |
| } |
| } |
| |
| /// The information about a candidate suggestion based on a getter or setter. |
| final class NameSuggestion extends CandidateSuggestion { |
| /// The name being suggested. |
| final String name; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [name]. |
| NameSuggestion({required this.name, required super.matcherScore}); |
| |
| @override |
| String get completion => name; |
| } |
| |
| /// The information about a candidate suggestion to create an override of an |
| /// inherited method. |
| final class OverrideSuggestion extends CandidateSuggestion |
| implements ElementBasedSuggestion { |
| @override |
| final ExecutableElement element; |
| |
| /// Whether `super` should be invoked in the body of the override. |
| final bool shouldInvokeSuper; |
| |
| /// If `true`, `@override` is already present, at least partially. |
| /// So, `@` is already present, and the override text does not need it. |
| final bool skipAt; |
| |
| /// The source range that should be replaced by the override. |
| final SourceRange replacementRange; |
| |
| /// Data required for the suggestion, computed when [CandidateSuggestion] |
| /// is converted to a completion item as per the protocol. |
| TypeImportData? data; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [element] |
| /// by inserting the [shouldInvokeSuper]. |
| OverrideSuggestion({ |
| required this.element, |
| required this.shouldInvokeSuper, |
| required this.skipAt, |
| required this.replacementRange, |
| required super.matcherScore, |
| }); |
| |
| @override |
| String get completion => |
| data?.completion ?? '@override ${element.displayName}'; |
| } |
| |
| /// The information about a candidate suggestion based on a field in a record |
| /// type. |
| final class RecordFieldSuggestion extends TypedSuggestion { |
| /// The field on which the suggestion is based. |
| final RecordTypeField field; |
| |
| /// The name of the field. |
| final String name; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [field] by |
| /// inserting the [name]. |
| RecordFieldSuggestion({ |
| required this.field, |
| required this.name, |
| required super.replacementRange, |
| required super.matcherScore, |
| super.addTypeAnnotation, |
| super.keyword, |
| }); |
| |
| @override |
| String get baseCompletion => name; |
| |
| @override |
| DartType get type => field.type; |
| } |
| |
| /// The information about a candidate suggestion based on a named field of |
| /// a record type. |
| final class RecordLiteralNamedFieldSuggestion extends CandidateSuggestion |
| with SuggestionData { |
| final RecordTypeNamedField field; |
| final bool appendColon; |
| final bool appendComma; |
| |
| RecordLiteralNamedFieldSuggestion.newField({ |
| required this.field, |
| required this.appendComma, |
| required super.matcherScore, |
| }) : appendColon = true; |
| |
| RecordLiteralNamedFieldSuggestion.onlyName({ |
| required this.field, |
| required super.matcherScore, |
| }) : appendColon = false, |
| appendComma = false; |
| |
| @override |
| String get completion { |
| _init(); |
| return _data!.completion; |
| } |
| |
| @override |
| String get displayText => completion; |
| |
| @override |
| void _init() { |
| if (_data != null) { |
| return; |
| } |
| var completion = field.name; |
| if (appendColon) { |
| completion += ': '; |
| } |
| var selectionOffset = completion.length; |
| |
| if (appendComma) { |
| completion += ','; |
| } |
| _data = _Data(completion, selectionOffset); |
| } |
| } |
| |
| sealed class ReplacementSuggestion extends CandidateSuggestion { |
| /// The source range that should be replaced by the suggestion. |
| final SourceRange replacementRange; |
| |
| ReplacementSuggestion({ |
| required super.matcherScore, |
| required this.replacementRange, |
| }); |
| } |
| |
| /// The information about a candidate suggestion for Flutter's `setState` method. |
| final class SetStateMethodSuggestion extends TypedExecutableSuggestion |
| with MemberSuggestion, SuggestionData { |
| @override |
| final MethodElement element; |
| |
| /// The element defined by the declaration in which the suggestion is to be |
| /// applied, or `null` if the completion is in a static context. |
| @override |
| final InterfaceElement? referencingInterface; |
| |
| /// The identation to be used for a multi-line completion. |
| final String indent; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [element]. |
| SetStateMethodSuggestion({ |
| required this.element, |
| required this.referencingInterface, |
| required this.indent, |
| required super.importData, |
| required super.matcherScore, |
| required super.replacementRange, |
| super.kind = CompletionSuggestionKind.INVOCATION, |
| super.addTypeAnnotation, |
| super.keyword, |
| }); |
| |
| @override |
| String get baseCompletion { |
| _init(); |
| return _data!.completion; |
| } |
| |
| @override |
| FunctionType get type => element.type; |
| |
| @override |
| void _init() { |
| if (_data != null) { |
| return; |
| } |
| // Build the completion and the selection offset. |
| var buffer = StringBuffer(); |
| |
| buffer.writeln('setState(() {'); |
| buffer.write('$indent '); |
| var selectionOffset = buffer.length; |
| buffer.writeln(); |
| buffer.write('$indent});'); |
| var completion = buffer.toString(); |
| _data = _Data(completion, selectionOffset, displayText: 'setState(() {});'); |
| } |
| } |
| |
| /// The information about a candidate suggestion based on a setter. |
| final class SetterSuggestion extends ImportableSuggestion |
| with MemberSuggestion { |
| @override |
| final SetterElement element; |
| |
| /// The element defined by the declaration in which the suggestion is to be |
| /// applied, or `null` if the completion is in a static context. |
| @override |
| final InterfaceElement? referencingInterface; |
| |
| /// Whether the accessor is being invoked with a target. |
| final bool withEnclosingName; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [element]. |
| SetterSuggestion({ |
| required this.element, |
| required super.importData, |
| required this.referencingInterface, |
| required super.matcherScore, |
| this.withEnclosingName = false, |
| }); |
| |
| @override |
| String get completion { |
| var prefix = _enclosingPrefix; |
| if (prefix.isNotEmpty) { |
| return '$prefix${element.displayName}'; |
| } |
| return element.displayName; |
| } |
| |
| /// Return the name of the enclosing class or extension. |
| /// |
| /// The enclosing element must be either a class, or extension; otherwise |
| /// we either fail with assertion, or return `null`. |
| String? get _enclosingClassOrExtensionName { |
| var enclosing = element.enclosingElement; |
| if (enclosing is InterfaceElement) { |
| return enclosing.displayName; |
| } else if (enclosing is ExtensionElement) { |
| return enclosing.displayName; |
| } else { |
| assert(false, 'Expected ClassElement or ExtensionElement'); |
| return null; |
| } |
| } |
| |
| String get _enclosingPrefix { |
| if (withEnclosingName) { |
| var enclosingName = _enclosingClassOrExtensionName; |
| return enclosingName != null ? '$enclosingName.' : ''; |
| } |
| return ''; |
| } |
| } |
| |
| /// The information about a candidate suggestion based on a static field in a |
| /// location where the name of the field must be qualified by the name of the |
| /// enclosing element. |
| final class StaticFieldSuggestion extends ImportableSuggestion |
| implements ElementBasedSuggestion { |
| @override |
| final FieldElement element; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [element]. |
| StaticFieldSuggestion({ |
| required super.importData, |
| required this.element, |
| required super.matcherScore, |
| }); |
| |
| @override |
| String get completion { |
| var enclosingElement = element.enclosingElement; |
| return '$completionPrefix${enclosingElement.displayName}.${element.displayName}'; |
| } |
| } |
| |
| /// Behavior common to suggestions where completion text, [selectionOffset], |
| /// and [displayText] is computed. |
| mixin SuggestionData { |
| _Data? _data; |
| |
| /// Text to be displayed in a completion pop-up. |
| String get displayText { |
| _init(); |
| return _data!.displayText; |
| } |
| |
| /// The offset, from the beginning of the inserted text, where the cursor |
| /// should be positioned. |
| int get selectionOffset { |
| _init(); |
| return _data!.selectionOffset; |
| } |
| |
| void _init(); |
| } |
| |
| /// The information about a candidate suggestion based on a parameter from a |
| /// super constructor. |
| final class SuperParameterSuggestion extends CandidateSuggestion |
| implements ElementBasedSuggestion { |
| @override |
| final FormalParameterElement element; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [element]. |
| SuperParameterSuggestion({ |
| required this.element, |
| required super.matcherScore, |
| }); |
| |
| @override |
| String get completion => element.displayName; |
| } |
| |
| /// The information about a candidate suggestion based on a top-level getter or |
| /// setter. |
| final class TopLevelFunctionSuggestion extends ExecutableSuggestion |
| implements ElementBasedSuggestion { |
| @override |
| final TopLevelFunctionElement element; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [element]. |
| TopLevelFunctionSuggestion({ |
| required super.importData, |
| required this.element, |
| required super.kind, |
| required super.matcherScore, |
| }); |
| |
| @override |
| String get completion => '$completionPrefix${element.displayName}'; |
| } |
| |
| /// The information about a candidate suggestion based on a top-level getter. |
| final class TopLevelGetterSuggestion extends ImportableSuggestion |
| implements ElementBasedSuggestion { |
| @override |
| final GetterElement element; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [element]. |
| TopLevelGetterSuggestion({ |
| required super.importData, |
| required this.element, |
| required super.matcherScore, |
| }); |
| |
| @override |
| String get completion => '$completionPrefix${element.displayName}'; |
| } |
| |
| /// The information about a candidate suggestion based on a top-level setter. |
| final class TopLevelSetterSuggestion extends ImportableSuggestion |
| implements ElementBasedSuggestion { |
| @override |
| final SetterElement element; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [element]. |
| TopLevelSetterSuggestion({ |
| required super.importData, |
| required this.element, |
| required super.matcherScore, |
| }); |
| |
| @override |
| String get completion => '$completionPrefix${element.displayName}'; |
| } |
| |
| /// The information about a candidate suggestion based on a top-level variable. |
| final class TopLevelVariableSuggestion extends ImportableSuggestion |
| implements ElementBasedSuggestion { |
| @override |
| final TopLevelVariableElement element; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [element]. |
| TopLevelVariableSuggestion({ |
| required super.importData, |
| required this.element, |
| required super.matcherScore, |
| }); |
| |
| @override |
| String get completion => '$completionPrefix${element.displayName}'; |
| } |
| |
| /// The information about a candidate suggestion based on a type alias. |
| final class TypeAliasSuggestion extends ImportableSuggestion |
| implements ElementBasedSuggestion { |
| @override |
| final TypeAliasElement element; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [element]. |
| TypeAliasSuggestion({ |
| required super.importData, |
| required this.element, |
| required super.matcherScore, |
| }); |
| |
| @override |
| String get completion => '$completionPrefix${element.displayName}'; |
| } |
| |
| sealed class TypedExecutableSuggestion extends ExecutableSuggestion |
| with TypedSuggestionCompletionMixin { |
| @override |
| final bool addTypeAnnotation; |
| |
| @override |
| final Keyword? keyword; |
| |
| @override |
| SourceRange replacementRange; |
| |
| @override |
| TypeImportData? data; |
| |
| TypedExecutableSuggestion({ |
| required this.replacementRange, |
| required super.importData, |
| required super.kind, |
| required super.matcherScore, |
| this.addTypeAnnotation = false, |
| this.keyword, |
| }); |
| } |
| |
| sealed class TypedImportableSuggestion extends ImportableSuggestion |
| with TypedSuggestionCompletionMixin { |
| @override |
| SourceRange replacementRange; |
| |
| @override |
| final bool addTypeAnnotation; |
| |
| @override |
| final Keyword? keyword; |
| |
| @override |
| TypeImportData? data; |
| |
| TypedImportableSuggestion({ |
| required super.importData, |
| required super.matcherScore, |
| required this.replacementRange, |
| this.addTypeAnnotation = false, |
| this.keyword, |
| }); |
| } |
| |
| sealed class TypedSuggestion extends ReplacementSuggestion { |
| final bool addTypeAnnotation; |
| |
| final Keyword? keyword; |
| |
| TypeImportData? data; |
| |
| TypedSuggestion({ |
| required super.matcherScore, |
| required super.replacementRange, |
| this.addTypeAnnotation = false, |
| this.keyword, |
| }); |
| |
| String get baseCompletion; |
| |
| @override |
| String get completion => data?.completion ?? baseCompletion; |
| |
| DartType? get type; |
| } |
| |
| mixin TypedSuggestionCompletionMixin on CandidateSuggestion |
| implements TypedSuggestion { |
| @override |
| String get completion => data?.completion ?? baseCompletion; |
| } |
| |
| /// Additional information needed for an [OverrideSuggestion] or a |
| /// [TypedSuggestion]. This should be computed when the |
| /// [CandidateSuggestion] is converted over to the completion item. |
| class TypeImportData { |
| final String completion; |
| |
| final String displayText; |
| |
| final Set<Uri> imports; |
| |
| final int? selectionOffset; |
| |
| final int? selectionLength; |
| |
| TypeImportData( |
| this.completion, |
| this.displayText, |
| this.imports, |
| this.selectionOffset, |
| this.selectionLength, |
| ); |
| } |
| |
| /// The information about a candidate suggestion based on a type parameter. |
| final class TypeParameterSuggestion extends CandidateSuggestion |
| implements ElementBasedSuggestion { |
| @override |
| final TypeParameterElement element; |
| |
| /// Initialize a newly created candidate suggestion to suggest the [element]. |
| TypeParameterSuggestion({required this.element, required super.matcherScore}); |
| |
| @override |
| String get completion => element.displayName; |
| } |
| |
| /// The URI suggestion. |
| final class UriSuggestion extends CandidateSuggestion { |
| final String uriStr; |
| |
| UriSuggestion({required this.uriStr, required super.matcherScore}); |
| |
| @override |
| String get completion => uriStr; |
| } |
| |
| /// Information computed for some the code completion suggestions. |
| class _Data { |
| String displayText; |
| |
| int selectionOffset; |
| |
| String completion; |
| |
| _Data(this.completion, this.selectionOffset, {this.displayText = ''}); |
| } |
| |
| extension on String { |
| (String, int?) get withoutCaret { |
| var caretIndex = indexOf('^'); |
| if (caretIndex < 0) { |
| return (this, null); |
| } else { |
| var rawText = substring(0, caretIndex) + substring(caretIndex + 1); |
| return (rawText, caretIndex); |
| } |
| } |
| } |
| |
| extension SuggestionBuilderExtension on SuggestionBuilder { |
| // TODO(brianwilkerson): Move these to `SuggestionBuilder`, possibly as part |
| // of splitting it into a legacy builder and an LSP builder. |
| |
| /// Add a suggestion based on the candidate [suggestion]. |
| Future<void> suggestFromCandidate(CandidateSuggestion suggestion) async { |
| if (suggestion is ImportableSuggestion) { |
| var importData = suggestion.importData; |
| if (importData != null) { |
| var uri = importData.libraryUri; |
| if (importData.isNotImported) { |
| isNotImportedLibrary = true; |
| requiredImports = [uri]; |
| } |
| libraryUriStr = uri.toString(); |
| } |
| } |
| |
| var relevance = relevanceComputer.computeRelevance(suggestion); |
| switch (suggestion) { |
| case TypedSuggestion(): |
| var data = await createTypedSuggestionData(suggestion, request); |
| requiredImports = data?.imports.toList() ?? requiredImports; |
| double? distance; |
| if (suggestion case MemberSuggestion(:var inheritanceDistance)) { |
| distance = inheritanceDistance(request.featureComputer); |
| } |
| var kind = |
| request.target.isFunctionalArgument() |
| ? CompletionSuggestionKind.IDENTIFIER |
| : null; |
| suggestion.data = data; |
| (switch (suggestion) { |
| FieldSuggestion(:var element) => |
| element.isEnumConstant |
| ? suggestEnumConstant( |
| element, |
| suggestion.completion, |
| relevance: relevance, |
| ) |
| : suggestField( |
| element, |
| inheritanceDistance: distance!, |
| relevance: relevance, |
| completion: suggestion.completion, |
| displayString: data?.displayText, |
| ), |
| GetterSuggestion() => suggestGetter( |
| suggestion.element, |
| displayString: data?.displayText, |
| inheritanceDistance: distance!, |
| relevance: relevance, |
| completion: suggestion.completion, |
| ), |
| SetStateMethodSuggestion() => suggestSetStateMethod( |
| suggestion.element, |
| kind: suggestion.kind, |
| completion: suggestion.completion, |
| displayText: data?.displayText ?? suggestion.displayText, |
| selectionOffset: suggestion.selectionOffset, |
| inheritanceDistance: distance!, |
| relevance: relevance, |
| ), |
| MethodSuggestion(kind: var suggestionKind) => |
| // TODO(brianwilkerson): Correctly set the kind of suggestion in cases |
| // where `isFunctionalArgument` would return `true` so we can stop |
| // using the `request.target`. |
| suggestMethod( |
| suggestion.element, |
| completion: suggestion.completion, |
| displayString: data?.displayText, |
| kind: kind ?? suggestionKind, |
| inheritanceDistance: distance!, |
| relevance: relevance, |
| ), |
| RecordFieldSuggestion() => suggestRecordField( |
| field: suggestion.field, |
| completion: suggestion.completion, |
| displayText: data?.displayText ?? suggestion.name, |
| relevance: relevance, |
| ), |
| // This is a workaround because mixins can't be `sealed`. |
| TypedSuggestionCompletionMixin() => null, |
| }); |
| case ClassSuggestion(): |
| suggestInterface( |
| suggestion.element, |
| prefix: suggestion.prefix, |
| relevance: relevance, |
| ); |
| case ClosureSuggestion(): |
| suggestClosure( |
| completion: suggestion.completion, |
| displayText: suggestion.displayText, |
| selectionOffset: suggestion.selectionOffset, |
| ); |
| case ConstructorSuggestion(): |
| var completion = suggestion.completion; |
| if (completion.isEmpty) { |
| break; |
| } |
| suggestConstructor( |
| suggestion.element, |
| hasClassName: suggestion.hasClassName, |
| completion: completion, |
| kind: suggestion.kind, |
| prefix: suggestion.prefix, |
| suggestUnnamedAsNew: suggestion.suggestUnnamedAsNew, |
| relevance: relevance, |
| ); |
| case EnumSuggestion(): |
| suggestInterface( |
| suggestion.element, |
| prefix: suggestion.prefix, |
| relevance: relevance, |
| ); |
| case EnumConstantSuggestion(): |
| if (suggestion.includeEnumName) { |
| suggestEnumConstant( |
| suggestion.element, |
| suggestion.completion, |
| relevance: relevance, |
| ); |
| } else { |
| suggestField( |
| suggestion.element, |
| inheritanceDistance: 0.0, |
| relevance: relevance, |
| ); |
| } |
| case ExtensionSuggestion(): |
| suggestExtension( |
| suggestion.element, |
| prefix: suggestion.prefix, |
| relevance: relevance, |
| kind: suggestion.kind, |
| ); |
| case ExtensionTypeSuggestion(): |
| suggestInterface( |
| suggestion.element, |
| prefix: suggestion.prefix, |
| relevance: relevance, |
| ); |
| case FormalParameterSuggestion(): |
| suggestFormalParameter( |
| element: suggestion.element, |
| distance: suggestion.distance, |
| relevance: relevance, |
| ); |
| case FunctionCall(): |
| suggestFunctionCall(); |
| case IdentifierSuggestion(): |
| suggestName( |
| suggestion.completion, |
| selectionOffset: suggestion.selectionOffset, |
| ); |
| case ImportPrefixSuggestion(): |
| suggestPrefix( |
| suggestion.libraryElement, |
| suggestion.prefixElement.displayName, |
| relevance: relevance, |
| ); |
| case KeywordSuggestion(): |
| suggestKeyword( |
| suggestion.completion, |
| offset: suggestion.selectionOffset, |
| relevance: relevance, |
| ); |
| case LabelSuggestion(): |
| suggestLabel(suggestion.label); |
| case LoadLibraryFunctionSuggestion(): |
| suggestLoadLibraryFunction(suggestion.element, kind: suggestion.kind); |
| case LocalFunctionSuggestion(): |
| suggestLocalFunction( |
| suggestion.element, |
| relevance: relevance, |
| kind: suggestion.kind, |
| ); |
| case LocalVariableSuggestion(): |
| suggestLocalVariable( |
| element: suggestion.element, |
| distance: suggestion.distance, |
| relevance: relevance, |
| ); |
| case MixinSuggestion(): |
| suggestInterface( |
| suggestion.element, |
| prefix: suggestion.prefix, |
| relevance: relevance, |
| ); |
| case NamedArgumentSuggestion(): |
| suggestNamedArgument( |
| suggestion.parameter, |
| appendColon: suggestion.appendColon, |
| appendComma: suggestion.appendComma, |
| replacementLength: suggestion.replacementLength, |
| completion: suggestion.completion, |
| selectionOffset: suggestion.selectionOffset, |
| relevance: relevance, |
| ); |
| case NameSuggestion(): |
| suggestName(suggestion.name); |
| case OverrideSuggestion(): |
| await suggestOverride( |
| element: suggestion.element, |
| invokeSuper: suggestion.shouldInvokeSuper, |
| replacementRange: suggestion.replacementRange, |
| skipAt: suggestion.skipAt, |
| ); |
| case RecordLiteralNamedFieldSuggestion(): |
| suggestNamedRecordField( |
| suggestion.field, |
| appendColon: suggestion.appendColon, |
| appendComma: suggestion.appendComma, |
| ); |
| case SetterSuggestion(): |
| var inheritanceDistance = suggestion.inheritanceDistance( |
| request.featureComputer, |
| ); |
| suggestSetter( |
| suggestion.element, |
| inheritanceDistance: inheritanceDistance, |
| relevance: relevance, |
| completion: suggestion.completion, |
| ); |
| case StaticFieldSuggestion(): |
| suggestStaticField( |
| suggestion.element, |
| prefix: suggestion.prefix, |
| relevance: relevance, |
| completion: suggestion.completion, |
| ); |
| case SuperParameterSuggestion(): |
| suggestSuperFormalParameter(suggestion.element); |
| case TopLevelFunctionSuggestion(): |
| suggestTopLevelFunction( |
| suggestion.element, |
| kind: suggestion.kind, |
| prefix: suggestion.prefix, |
| relevance: relevance, |
| ); |
| case TopLevelGetterSuggestion(): |
| suggestTopLevelGetter( |
| suggestion.element, |
| prefix: suggestion.prefix, |
| relevance: relevance, |
| ); |
| case TopLevelSetterSuggestion(): |
| suggestTopLevelSetter( |
| suggestion.element, |
| prefix: suggestion.prefix, |
| relevance: relevance, |
| ); |
| case TopLevelVariableSuggestion(): |
| suggestTopLevelVariable( |
| suggestion.element, |
| prefix: suggestion.prefix, |
| relevance: relevance, |
| ); |
| case TypeAliasSuggestion(): |
| suggestTypeAlias( |
| suggestion.element, |
| prefix: suggestion.prefix, |
| relevance: relevance, |
| ); |
| case TypeParameterSuggestion(): |
| suggestTypeParameter(suggestion.element, relevance: relevance); |
| case UriSuggestion(): |
| suggestUri(suggestion.uriStr); |
| } |
| isNotImportedLibrary = false; |
| libraryUriStr = null; |
| requiredImports = []; |
| } |
| |
| /// Add a suggestion for each of the candidate [suggestions]. |
| Future<void> suggestFromCandidates( |
| List<CandidateSuggestion> suggestions, |
| bool preferConstants, |
| String? completionLocation, |
| ) async { |
| relevanceComputer.preferConstants = |
| preferConstants || request.inConstantContext; |
| relevanceComputer.completionLocation = completionLocation; |
| for (var suggestion in suggestions) { |
| await suggestFromCandidate(suggestion); |
| } |
| } |
| } |