| // 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. |
| |
| import 'package:analysis_server/src/protocol_server.dart' |
| show CompletionSuggestionKind; |
| import 'package:analysis_server/src/services/completion/dart/candidate_suggestion.dart'; |
| import 'package:analysis_server/src/services/completion/dart/completion_manager.dart'; |
| import 'package:analysis_server/src/services/completion/dart/completion_state.dart'; |
| import 'package:analysis_server/src/services/completion/dart/not_imported_completion_pass.dart'; |
| import 'package:analysis_server/src/services/completion/dart/suggestion_collector.dart'; |
| import 'package:analysis_server/src/services/completion/dart/visibility_tracker.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/src/dart/ast/extensions.dart'; |
| import 'package:analyzer/src/dart/element/element.dart'; |
| import 'package:analyzer/src/dart/element/member.dart'; |
| import 'package:analyzer/src/dart/element/type_algebra.dart'; |
| import 'package:analyzer/src/dart/resolver/applicable_extensions.dart'; |
| import 'package:analyzer/src/dart/resolver/scope.dart'; |
| import 'package:analyzer/src/utilities/extensions/element.dart'; |
| import 'package:analyzer/src/workspace/pub.dart'; |
| |
| /// A helper class that produces candidate suggestions for all of the |
| /// declarations that are in scope at the completion location. |
| class DeclarationHelper { |
| /// The regular expression used to detect an unused identifier (a sequence of |
| /// one or more underscores with no other characters). |
| static final RegExp UnusedIdentifier = RegExp(r'^_+$'); |
| |
| /// The completion request being processed. |
| final DartCompletionRequest request; |
| |
| /// The suggestion collector to which suggestions will be added. |
| final SuggestionCollector collector; |
| |
| /// The state used to compute the candidate suggestions. |
| final CompletionState state; |
| |
| /// The offset of the completion location. |
| final int offset; |
| |
| /// The visibility tracker used to prevent suggesting elements that have been |
| /// shadowed by local declarations. |
| final VisibilityTracker visibilityTracker = VisibilityTracker(); |
| |
| /// Whether suggestions should be limited to only include those to which a |
| /// value can be assigned: either a setter or a local variable. |
| final bool mustBeAssignable; |
| |
| /// Whether suggestions should be limited to only include valid constants. |
| final bool mustBeConstant; |
| |
| /// Whether suggestions should be limited to only include interface types that |
| /// can be extended in the current library. |
| final bool mustBeExtendable; |
| |
| /// Whether suggestions should be limited to only include interface types that |
| /// can be implemented in the current library. |
| final bool mustBeImplementable; |
| |
| /// Whether suggestions should be limited to only include interface types that |
| /// can be mixed in in the current library. |
| final bool mustBeMixable; |
| |
| /// Whether suggestions should be limited to only include methods with a |
| /// non-`void` return type. |
| final bool mustBeNonVoid; |
| |
| /// AWhether suggestions should be limited to only include static members. |
| final bool mustBeStatic; |
| |
| /// Whether suggestions should be limited to only include types. |
| final bool mustBeType; |
| |
| /// Whether suggestions should exclude type names, e.g. include only |
| /// constructor invocations. |
| final bool excludeTypeNames; |
| |
| /// Whether object patterns are allowed even when the context otherwise |
| /// requires a constant. |
| /// |
| /// Ignored when [mustBeConstant] is `false`. |
| final bool objectPatternAllowed; |
| |
| /// Whether suggestions should be tear-offs rather than invocations where |
| /// possible. |
| final bool preferNonInvocation; |
| |
| /// Whether unnamed constructors should be suggested as `.new`. |
| final bool suggestUnnamedAsNew; |
| |
| /// Whether the generation of suggestions for imports should be skipped. This |
| /// exists as a temporary measure that will be removed after all of the |
| /// suggestions are being produced by the various passes. |
| final bool skipImports; |
| |
| /// The nodes that should be excluded, for example because we identified |
| /// that they were created during parsing recovery, and don't contain |
| /// useful suggestions. |
| final Set<AstNode> excludedNodes; |
| |
| /// The number of local variables that have already been suggested. |
| int _variableDistance = 0; |
| |
| /// The operations to be performed in the [NotImportedCompletionPass]. |
| /// |
| /// The list will be empty if the pass does not need to be run. |
| List<NotImportedOperation> notImportedOperations = []; |
| |
| /// Initialize a newly created helper to add suggestions to the [collector] |
| /// that are appropriate for the location at the [offset]. |
| /// |
| /// The flags [mustBeAssignable], [mustBeConstant], [mustBeNonVoid], |
| /// [mustBeStatic], and [mustBeType] are used to control which declarations |
| /// are suggested. The flag [preferNonInvocation] is used to control what kind |
| /// of suggestion is made for executable elements. |
| /// |
| /// The flag [skipImports] is a temporary measure that will be removed after |
| /// all of the suggestions are being produced by the various passes. |
| DeclarationHelper({ |
| required this.request, |
| required this.collector, |
| required this.state, |
| required this.offset, |
| required this.mustBeAssignable, |
| required this.mustBeConstant, |
| required this.mustBeExtendable, |
| required this.mustBeImplementable, |
| required this.mustBeMixable, |
| required this.mustBeNonVoid, |
| required this.mustBeStatic, |
| required this.mustBeType, |
| required this.excludeTypeNames, |
| required this.objectPatternAllowed, |
| required this.preferNonInvocation, |
| required this.suggestUnnamedAsNew, |
| required this.skipImports, |
| required this.excludedNodes, |
| }); |
| |
| /// Return the suggestion kind that should be used for executable elements. |
| CompletionSuggestionKind get _executableSuggestionKind => preferNonInvocation |
| ? CompletionSuggestionKind.IDENTIFIER |
| : CompletionSuggestionKind.INVOCATION; |
| |
| /// Add any constructors that are visible within the current library. |
| void addConstructorInvocations() { |
| var library = request.libraryElement; |
| var importData = ImportData( |
| libraryUri: library.source.uri, prefix: null, isNotImported: false); |
| _addConstructors(library, importData); |
| if (!skipImports) { |
| _addImportedConstructors(library); |
| _recordOperation(ConstructorsOperation(declarationHelper: this)); |
| } |
| } |
| |
| /// Add suggestions for all constructors of [element]. |
| void addConstructorNamesForElement({ |
| required InterfaceElement element, |
| }) { |
| var constructors = element.augmented.constructors; |
| for (var constructor in constructors) { |
| _suggestConstructor( |
| constructor, |
| hasClassName: true, |
| importData: null, |
| isConstructorRedirect: false, |
| ); |
| } |
| } |
| |
| /// Add suggestions for all of the named constructors in the [type]. If |
| /// [exclude] is not `null` it is the name of a constructor that should be |
| /// omitted from the list, typically because suggesting it would result in an |
| /// infinite loop. |
| void addConstructorNamesForType( |
| {required InterfaceType type, String? exclude}) { |
| for (var constructor in type.constructors) { |
| var name = constructor.name; |
| if (name.isNotEmpty && |
| name != exclude && |
| !(mustBeConstant && !constructor.isConst)) { |
| _suggestConstructor( |
| constructor, |
| hasClassName: true, |
| importData: null, |
| isConstructorRedirect: false, |
| ); |
| } |
| } |
| } |
| |
| /// Add suggestions for declarations through [prefixElement]. |
| void addDeclarationsThroughImportPrefix(PrefixElement prefixElement) { |
| for (var importElement in prefixElement.imports) { |
| var importedLibrary = importElement.importedLibrary; |
| if (importedLibrary == null) { |
| continue; |
| } |
| |
| _addDeclarationsImportedFrom( |
| library: importedLibrary, |
| namespace: importElement.namespace, |
| prefix: null, |
| ); |
| |
| if (importElement.prefix case var importPrefix?) { |
| if (importPrefix is DeferredImportElementPrefix) { |
| var matcherScore = state.matcher.score('loadLibrary'); |
| if (matcherScore != -1) { |
| collector.addSuggestion( |
| LoadLibraryFunctionSuggestion( |
| kind: CompletionSuggestionKind.INVOCATION, |
| element: importedLibrary.loadLibraryFunction, |
| matcherScore: matcherScore, |
| ), |
| ); |
| } |
| } |
| } |
| } |
| } |
| |
| /// Add any fields that can be initialized in the initializer list of the |
| /// given [constructor]. If a [fieldToInclude] is provided, then it should not |
| /// be skipped because the cursor is inside that field's name. |
| void addFieldsForInitializers( |
| ConstructorDeclaration constructor, FieldElement? fieldToInclude) { |
| var containingElement = constructor.declaredElement?.enclosingElement; |
| if (containingElement == null) { |
| return; |
| } |
| |
| var fieldsToSkip = <FieldElement>{}; |
| // Skip fields that are already initialized in the initializer list. |
| for (var initializer in constructor.initializers) { |
| if (initializer is ConstructorFieldInitializer) { |
| var fieldElement = initializer.fieldName.staticElement; |
| if (fieldElement is FieldElement) { |
| fieldsToSkip.add(fieldElement); |
| } |
| } |
| } |
| // Skip fields that are already initialized in the parameter list. |
| for (var parameter in constructor.parameters.parameters) { |
| parameter = parameter.notDefault; |
| if (parameter is FieldFormalParameter) { |
| var parameterElement = parameter.declaredElement; |
| if (parameterElement is FieldFormalParameterElement) { |
| var field = parameterElement.field; |
| if (field != null) { |
| fieldsToSkip.add(field); |
| } |
| } |
| } |
| } |
| fieldsToSkip.remove(fieldToInclude); |
| |
| for (var field in containingElement.fields) { |
| // Skip fields that are already initialized at their declaration. |
| if (!field.isStatic && |
| !field.isSynthetic && |
| !fieldsToSkip.contains(field) && |
| (!(field.isFinal || field.isConst) || !field.hasInitializer)) { |
| _suggestField(field: field); |
| } |
| } |
| } |
| |
| /// Add suggestions for all of the top-level declarations that are exported |
| /// from the [library] except for those whose name is in the set of |
| /// [excludedNames]. |
| void addFromLibrary(LibraryElement library, Set<String> excludedNames) { |
| for (var entry in library.exportNamespace.definedNames.entries) { |
| if (!excludedNames.contains(entry.key)) { |
| _addImportedElement(entry.value); |
| } |
| } |
| } |
| |
| /// Adds suggestions for the getters defined by the [type], except for those |
| /// whose names are in the set of [excludedGetters]. |
| void addGetters( |
| {required DartType type, required Set<String> excludedGetters}) { |
| if (type is InterfaceType) { |
| _addInstanceMembers( |
| type: type, |
| excludedGetters: excludedGetters, |
| includeMethods: false, |
| includeSetters: false); |
| } else if (type is RecordType) { |
| _addFieldsOfRecordType( |
| type: type, |
| excludedFields: excludedGetters, |
| ); |
| _addMembersOfDartCoreObject(); |
| } |
| } |
| |
| void addImportPrefixes() { |
| var library = request.libraryElement; |
| for (var element in library.libraryImports) { |
| var importPrefix = element.prefix; |
| if (importPrefix == null) { |
| continue; |
| } |
| |
| var prefixElement = importPrefix.element; |
| if (!visibilityTracker.isVisible( |
| element: prefixElement, importData: null)) { |
| continue; |
| } |
| |
| if (prefixElement.name.isEmpty) { |
| continue; |
| } |
| |
| var importedLibrary = element.importedLibrary; |
| if (importedLibrary == null) { |
| continue; |
| } |
| var matcherScore = state.matcher.score(prefixElement.displayName); |
| if (matcherScore != -1) { |
| collector.addSuggestion( |
| ImportPrefixSuggestion( |
| libraryElement: importedLibrary, |
| prefixElement: prefixElement, |
| matcherScore: matcherScore, |
| ), |
| ); |
| } |
| } |
| } |
| |
| /// Add any instance members defined for the given [type]. |
| /// |
| /// If [onlySuper] is `true`, then only the members that are valid after a |
| /// `super` expression (those from superclasses) will be added. |
| void addInstanceMembersOfType(DartType type, {bool onlySuper = false}) { |
| if (type is TypeParameterType) { |
| type = type.bound; |
| } |
| if (type is InterfaceType) { |
| _addInstanceMembers( |
| type: type, |
| excludedGetters: const {}, |
| includeMethods: !mustBeAssignable, |
| includeSetters: true, |
| onlySuper: onlySuper); |
| } else if (type is RecordType) { |
| _addFieldsOfRecordType( |
| type: type, |
| excludedFields: const {}, |
| ); |
| _addMembersOfDartCoreObject(); |
| } else if (type is FunctionType) { |
| _suggestFunctionCall(); |
| _addMembersOfDartCoreObject(); |
| } else if (type is DynamicType) { |
| _addMembersOfDartCoreObject(); |
| } |
| } |
| |
| /// Add any declarations that are visible at the completion location, |
| /// given that the completion location is within the [node]. This includes |
| /// local variables, local functions, parameters, members of the enclosing |
| /// declaration, and top-level declarations in the enclosing library. |
| void addLexicalDeclarations(AstNode node) { |
| var containingMember = |
| mustBeType ? _addLocalTypes(node) : _addLocalDeclarations(node); |
| if (containingMember == null) { |
| return; |
| } |
| AstNode? parent = containingMember.parent ?? containingMember; |
| if (parent is ClassMember) { |
| assert(node is CommentReference); |
| parent = parent.parent; |
| } else if (parent is CompilationUnit) { |
| parent = containingMember; |
| } |
| CompilationUnitMember? topLevelMember; |
| if (parent is CompilationUnitMember) { |
| topLevelMember = parent; |
| _addMembersOfEnclosingNode(parent); |
| parent = parent.parent; |
| } |
| if (parent is CompilationUnit) { |
| var library = parent.declaredElement?.library; |
| if (library != null) { |
| _addTopLevelDeclarations(library); |
| addImportPrefixes(); |
| if (!skipImports) { |
| _addImportedDeclarations(library); |
| } |
| _recordOperation(StaticMembersOperation(declarationHelper: this)); |
| } |
| } |
| if (topLevelMember != null && !mustBeStatic && !mustBeType) { |
| _addInheritedMembers(topLevelMember); |
| } |
| } |
| |
| /// Add members from the given [ExtensionElement]. |
| void addMembersFromExtensionElement(ExtensionElement extension, |
| {ImportData? importData}) { |
| var extendedType = extension.extendedType; |
| var referencingInterface = |
| (extendedType is InterfaceType) ? extendedType.element : null; |
| for (var method in extension.methods) { |
| if (!method.isStatic) { |
| _suggestMethod( |
| method: method, |
| importData: importData, |
| referencingInterface: referencingInterface); |
| } |
| } |
| for (var accessor in extension.accessors) { |
| if (!accessor.isStatic) { |
| _suggestProperty( |
| accessor: accessor, |
| importData: importData, |
| referencingInterface: referencingInterface); |
| } |
| } |
| } |
| |
| /// Adds suggestions for any constructors that are visible within the not yet |
| /// imported [library]. |
| void addNotImportedConstructors(LibraryElement library) { |
| var importData = ImportData( |
| libraryUri: library.source.uri, |
| prefix: null, |
| isNotImported: true, |
| ); |
| _addConstructors(library, importData); |
| } |
| |
| /// Add members from all the applicable extensions that are visible in the |
| /// not yet imported [library] that are applicable for the given [type]. |
| void addNotImportedExtensionMethods( |
| {required LibraryElement library, |
| required InterfaceType type, |
| required Set<String> excludedGetters, |
| required bool includeMethods, |
| required bool includeSetters}) { |
| var applicableExtensions = library.accessibleExtensions.applicableTo( |
| targetLibrary: library, |
| // Ignore nullability, consistent with non-extension members. |
| targetType: type.isDartCoreNull |
| ? type |
| : library.typeSystem.promoteToNonNull(type), |
| strictCasts: false, |
| ); |
| var importData = ImportData( |
| libraryUri: library.source.uri, prefix: null, isNotImported: true); |
| for (var instantiatedExtension in applicableExtensions) { |
| var extension = instantiatedExtension.extension; |
| if (extension.isVisibleIn(request.libraryElement)) { |
| addMembersFromExtensionElement(extension, importData: importData); |
| } |
| } |
| } |
| |
| /// Adds suggestions for any top-level declarations that are visible within |
| /// the not yet imported [library]. |
| void addNotImportedTopLevelDeclarations(LibraryElement library) { |
| var importData = ImportData( |
| libraryUri: library.source.uri, |
| prefix: null, |
| isNotImported: true, |
| ); |
| _addExternalTopLevelDeclarations( |
| library: library, |
| namespace: library.exportNamespace, |
| importData: importData, |
| ); |
| } |
| |
| /// Add any parameters from the super constructor of the constructor |
| /// containing the [node] that can be referenced as a super parameter. |
| void addParametersFromSuperConstructor(SuperFormalParameter node) { |
| var element = node.declaredElement; |
| if (element is! SuperFormalParameterElementImpl) { |
| return; |
| } |
| |
| var constructor = node.thisOrAncestorOfType<ConstructorDeclaration>(); |
| if (constructor == null) { |
| return; |
| } |
| |
| var constructorElement = constructor.declaredElement; |
| if (constructorElement is! ConstructorElementImpl) { |
| return; |
| } |
| |
| var superConstructor = constructorElement.superConstructor; |
| if (superConstructor == null) { |
| return; |
| } |
| |
| if (node.isNamed) { |
| var superConstructorInvocation = constructor.initializers |
| .whereType<SuperConstructorInvocation>() |
| .singleOrNull; |
| var specified = <String>{ |
| ...constructorElement.parameters.map((e) => e.name), |
| ...?superConstructorInvocation?.argumentList.arguments |
| .whereType<NamedExpression>() |
| .map((e) => e.name.label.name), |
| }; |
| for (var superParameter in superConstructor.parameters) { |
| if (superParameter.isNamed && |
| !specified.contains(superParameter.name)) { |
| _suggestSuperParameter(superParameter); |
| } |
| } |
| } else if (node.isPositional) { |
| var indexOfThis = element.indexIn(constructorElement); |
| var superPositionalList = superConstructor.parameters |
| .where((parameter) => parameter.isPositional) |
| .toList(); |
| if (indexOfThis >= 0 && indexOfThis < superPositionalList.length) { |
| var superPositional = superPositionalList[indexOfThis]; |
| _suggestSuperParameter(superPositional); |
| } |
| } |
| } |
| |
| /// Add suggestions for all of the constructor in the [library] that could be |
| /// a redirection target for the [redirectingConstructor]. |
| void addPossibleRedirectionsInLibrary( |
| ConstructorElement redirectingConstructor, LibraryElement library) { |
| var classElement = |
| redirectingConstructor.enclosingElement.augmented.declaration; |
| var classType = classElement.thisType; |
| var typeSystem = library.typeSystem; |
| for (var unit in library.units) { |
| for (var classElement in unit.classes) { |
| if (typeSystem.isSubtypeOf(classElement.thisType, classType)) { |
| for (var constructor in classElement.constructors) { |
| if (constructor != redirectingConstructor && |
| constructor.isAccessibleIn(library)) { |
| _suggestConstructor( |
| constructor, |
| hasClassName: false, |
| importData: null, |
| isConstructorRedirect: true, |
| ); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /// Add any static members defined by the given [element]. |
| void addStaticMembersOfElement(Element element) { |
| if (element is TypeAliasElement) { |
| var aliasedType = element.aliasedType; |
| if (aliasedType is InterfaceType) { |
| element = aliasedType.element; |
| } |
| } |
| switch (element) { |
| case EnumElement(): |
| var augmented = element.augmented; |
| _addStaticMembers( |
| accessors: augmented.accessors, |
| constructors: augmented.constructors, |
| containingElement: element, |
| fields: augmented.fields, |
| methods: augmented.methods, |
| ); |
| case ExtensionElement(): |
| var augmented = element.augmented; |
| _addStaticMembers( |
| accessors: augmented.accessors, |
| constructors: const [], |
| containingElement: element, |
| fields: augmented.fields, |
| methods: augmented.methods, |
| ); |
| case InterfaceElement(): |
| var augmented = element.augmented; |
| _addStaticMembers( |
| accessors: augmented.accessors, |
| constructors: augmented.constructors, |
| containingElement: element, |
| fields: augmented.fields, |
| methods: augmented.methods, |
| ); |
| } |
| } |
| |
| /// Adds suggestions for any constructors that are declared within the |
| /// [library]. |
| void _addConstructors(LibraryElement library, ImportData importData) { |
| for (var unit in library.units) { |
| // Mixins don't have constructors, so we don't need to enumerate them. |
| for (var element in unit.classes) { |
| _suggestConstructors(element.constructors, importData, |
| allowNonFactory: !element.isAbstract); |
| } |
| for (var element in unit.enums) { |
| _suggestConstructors(element.constructors, importData); |
| } |
| for (var element in unit.extensionTypes) { |
| _suggestConstructors(element.constructors, importData); |
| } |
| for (var element in unit.typeAliases) { |
| _addConstructorsForAliasedElement(element, importData); |
| } |
| } |
| } |
| |
| /// Adds suggestions for any constructors that are visible through type |
| /// aliases declared within the [library]. |
| void _addConstructorsForAliasedElement( |
| TypeAliasElement alias, ImportData? importData) { |
| var aliasedElement = alias.aliasedElement; |
| if (aliasedElement is ClassElement) { |
| _suggestConstructors(aliasedElement.constructors, importData, |
| allowNonFactory: !aliasedElement.isAbstract); |
| } else if (aliasedElement is ExtensionTypeElement) { |
| _suggestConstructors(aliasedElement.constructors, importData); |
| } else if (aliasedElement is MixinElement) { |
| _suggestConstructors(aliasedElement.constructors, importData); |
| } |
| } |
| |
| /// Adds suggestions for any constructors that are visible within the |
| /// [library]. |
| void _addConstructorsImportedFrom({ |
| required LibraryElement library, |
| required Namespace namespace, |
| required String? prefix, |
| }) { |
| var importData = ImportData( |
| libraryUri: library.source.uri, prefix: prefix, isNotImported: false); |
| for (var element in namespace.definedNames.values) { |
| switch (element) { |
| case ClassElement(): |
| _suggestConstructors(element.constructors, importData, |
| allowNonFactory: !element.isAbstract); |
| case ExtensionTypeElement(): |
| _suggestConstructors(element.constructors, importData); |
| case TypeAliasElement(): |
| _addConstructorsForAliasedElement(element, importData); |
| } |
| } |
| } |
| |
| /// Adds suggestions for any top-level declarations that are visible within |
| /// the [library]. |
| void _addDeclarationsImportedFrom({ |
| required LibraryElement library, |
| required Namespace namespace, |
| required String? prefix, |
| }) { |
| var importData = ImportData( |
| libraryUri: library.source.uri, |
| prefix: prefix, |
| isNotImported: false, |
| ); |
| _addExternalTopLevelDeclarations( |
| library: library, |
| namespace: namespace, |
| importData: importData, |
| ); |
| } |
| |
| /// Add members from all the applicable extensions that are visible for the |
| /// given [InterfaceType]. |
| void _addExtensionMembers( |
| {required InterfaceType type, |
| required Set<String> excludedGetters, |
| required bool includeMethods, |
| required bool includeSetters}) { |
| var libraryElement = request.libraryElement; |
| |
| var applicableExtensions = libraryElement.accessibleExtensions.applicableTo( |
| targetLibrary: libraryElement, |
| // Ignore nullability, consistent with non-extension members. |
| targetType: type.isDartCoreNull |
| ? type |
| : libraryElement.typeSystem.promoteToNonNull(type), |
| strictCasts: false, |
| ); |
| for (var instantiatedExtension in applicableExtensions) { |
| var extension = instantiatedExtension.extension; |
| if (includeMethods) { |
| for (var method in extension.methods) { |
| if (!method.isStatic) { |
| _suggestMethod(method: method); |
| } |
| } |
| } |
| for (var accessor in extension.accessors) { |
| if (accessor.isGetter || includeSetters && accessor.isSetter) { |
| _suggestProperty(accessor: accessor); |
| } |
| } |
| } |
| } |
| |
| /// Adds suggestions for any top-level declarations that are visible within |
| /// the [library]. |
| /// |
| /// The [library] is a library other than the library in which completion is |
| /// being requested. |
| /// |
| /// The [namespace] is the export namespace of the [library]. |
| /// |
| /// The [importData] indicates how the library is, or should be, imported. |
| void _addExternalTopLevelDeclarations( |
| {required LibraryElement library, |
| required Namespace namespace, |
| required ImportData importData}) { |
| for (var element in namespace.definedNames.values) { |
| switch (element) { |
| case ClassElement(): |
| _suggestClass(element, importData); |
| case EnumElement(): |
| _suggestEnum(element, importData); |
| case ExtensionElement(): |
| if (!mustBeType) { |
| _suggestExtension(element, importData); |
| } |
| case ExtensionTypeElement(): |
| _suggestExtensionType(element, importData); |
| case FunctionElement(): |
| if (!mustBeType) { |
| _suggestTopLevelFunction(element, importData); |
| } |
| case MixinElement(): |
| _suggestMixin(element, importData); |
| case PropertyAccessorElement(): |
| if (!mustBeType) { |
| // Do not add synthetic setters, as these may prevent adding getters, |
| // they are both tracked with the same name in the |
| // [VisibilityTracker]. |
| if (element.isSynthetic && element.isSetter) { |
| break; |
| } |
| _suggestTopLevelProperty(element, importData); |
| } |
| case TopLevelVariableElement(): |
| if (!mustBeType) { |
| _suggestTopLevelVariable(element, importData); |
| } |
| case TypeAliasElement(): |
| _suggestTypeAlias(element, importData); |
| } |
| } |
| } |
| |
| /// Add suggestions for any of the fields defined by the record [type] except |
| /// for those whose names are in the set of [excludedFields]. |
| void _addFieldsOfRecordType({ |
| required RecordType type, |
| required Set<String> excludedFields, |
| }) { |
| for (var (index, field) in type.positionalFields.indexed) { |
| _suggestRecordField( |
| field: field, |
| name: '\$${index + 1}', |
| ); |
| } |
| |
| for (var field in type.namedFields) { |
| if (!excludedFields.contains(field.name)) { |
| _suggestRecordField( |
| field: field, |
| name: field.name, |
| ); |
| } |
| } |
| } |
| |
| /// Adds suggestions for any constructors that are imported into the |
| /// [library]. |
| void _addImportedConstructors(LibraryElement library) { |
| // TODO(brianwilkerson): This will create suggestions for elements that |
| // conflict with different elements imported from a different library. Not |
| // sure whether that's the desired behavior. |
| for (var importElement in library.libraryImports) { |
| var importedLibrary = importElement.importedLibrary; |
| if (importedLibrary != null) { |
| _addConstructorsImportedFrom( |
| library: importedLibrary, |
| namespace: importElement.namespace, |
| prefix: importElement.prefix?.element.name, |
| ); |
| } |
| } |
| } |
| |
| /// Adds suggestions for any top-level declarations that are imported into the |
| /// [library]. |
| void _addImportedDeclarations(LibraryElement library) { |
| // TODO(brianwilkerson): This will create suggestions for elements that |
| // conflict with different elements imported from a different library. Not |
| // sure whether that's the desired behavior. |
| for (var importElement in library.libraryImports) { |
| var importedLibrary = importElement.importedLibrary; |
| if (importedLibrary != null) { |
| _addDeclarationsImportedFrom( |
| library: importedLibrary, |
| namespace: importElement.namespace, |
| prefix: importElement.prefix?.element.name, |
| ); |
| if (importedLibrary.isDartCore && mustBeType) { |
| var name = 'Never'; |
| var matcherScore = state.matcher.score(name); |
| if (matcherScore != -1) { |
| collector.addSuggestion( |
| NameSuggestion(name: name, matcherScore: matcherScore)); |
| } |
| } |
| } |
| } |
| } |
| |
| /// Adds a suggestion for the top-level [element]. |
| void _addImportedElement(Element element) { |
| var matcherScore = state.matcher.score(element.displayName); |
| if (matcherScore != -1) { |
| var suggestion = switch (element) { |
| ClassElement() => ClassSuggestion( |
| importData: null, element: element, matcherScore: matcherScore), |
| EnumElement() => EnumSuggestion( |
| importData: null, element: element, matcherScore: matcherScore), |
| ExtensionElement() => ExtensionSuggestion( |
| importData: null, element: element, matcherScore: matcherScore), |
| ExtensionTypeElement() => ExtensionTypeSuggestion( |
| importData: null, element: element, matcherScore: matcherScore), |
| FunctionElement() => TopLevelFunctionSuggestion( |
| importData: null, |
| element: element, |
| kind: _executableSuggestionKind, |
| matcherScore: matcherScore), |
| MixinElement() => MixinSuggestion( |
| importData: null, element: element, matcherScore: matcherScore), |
| PropertyAccessorElement() => TopLevelPropertyAccessSuggestion( |
| importData: null, element: element, matcherScore: matcherScore), |
| TopLevelVariableElement() => TopLevelVariableSuggestion( |
| importData: null, element: element, matcherScore: matcherScore), |
| TypeAliasElement() => TypeAliasSuggestion( |
| importData: null, element: element, matcherScore: matcherScore), |
| _ => null |
| }; |
| if (suggestion != null) { |
| collector.addSuggestion(suggestion); |
| } |
| } |
| } |
| |
| /// Adds suggestions for any instance members inherited by the |
| /// [containingMember]. |
| void _addInheritedMembers(CompilationUnitMember containingMember) { |
| var element = switch (containingMember) { |
| ClassDeclaration() => containingMember.declaredElement, |
| EnumDeclaration() => containingMember.declaredElement, |
| ExtensionDeclaration() => containingMember.declaredElement, |
| ExtensionTypeDeclaration() => containingMember.declaredElement, |
| MixinDeclaration() => containingMember.declaredElement, |
| ClassTypeAlias() => containingMember.declaredElement, |
| GenericTypeAlias() => containingMember.declaredElement, |
| _ => null, |
| }; |
| if (!mustBeStatic && element is ExtensionElement) { |
| var thisType = element.thisType; |
| if (thisType is InterfaceType) { |
| _addInstanceMembers( |
| type: thisType, |
| excludedGetters: {}, |
| includeMethods: true, |
| includeSetters: true); |
| } |
| return; |
| } |
| if (element is! InterfaceElement) { |
| return; |
| } |
| var referencingInterface = _referencingInterfaceFor(element); |
| var members = request.inheritanceManager.getInheritedMap2(element); |
| for (var member in members.values) { |
| switch (member) { |
| case MethodElement(): |
| _suggestMethod( |
| method: member, |
| referencingInterface: referencingInterface, |
| ); |
| case PropertyAccessorElement(): |
| _suggestProperty( |
| accessor: member, |
| referencingInterface: referencingInterface, |
| ); |
| } |
| } |
| } |
| |
| /// Adds completion suggestions for instance members of the given [type]. |
| /// |
| /// Suggestions will not be added for any getters whose names are in the set |
| /// of [excludedGetters]. |
| /// |
| /// Suggestions for methods will only be added if [includeMethods] is `true`. |
| /// |
| /// Suggestions for setters will only be added if [includeSetters] is `true`. |
| /// |
| /// If [onlySuper] is `true`, only valid super members will be suggested. |
| void _addInstanceMembers( |
| {required InterfaceType type, |
| required Set<String> excludedGetters, |
| required bool includeMethods, |
| required bool includeSetters, |
| bool onlySuper = false}) { |
| var substitution = Substitution.fromInterfaceType(type); |
| var map = onlySuper |
| ? request.inheritanceManager.getInheritedConcreteMap2(type.element) |
| : request.inheritanceManager.getInterface(type.element).map; |
| |
| var membersByName = <String, List<ExecutableElement>>{}; |
| for (var rawMember in map.values) { |
| if (_canAccessInstanceMember(rawMember)) { |
| var name = rawMember.displayName; |
| membersByName |
| .putIfAbsent(name, () => <ExecutableElement>[]) |
| .add(rawMember); |
| } |
| } |
| var referencingInterface = _referencingInterfaceFor(type.element); |
| for (var entry in membersByName.entries) { |
| var members = entry.value; |
| var rawMember = members.bestMember; |
| if (rawMember is MethodElement) { |
| if (includeMethods) { |
| // Exclude static methods when completion on an instance. |
| var member = ExecutableMember.from2(rawMember, substitution); |
| _suggestMethod( |
| method: member as MethodElement, |
| referencingInterface: referencingInterface, |
| ); |
| } |
| } else if (rawMember is PropertyAccessorElement) { |
| if (rawMember.isGetter && !excludedGetters.contains(entry.key) || |
| includeSetters && rawMember.isSetter) { |
| var member = ExecutableMember.from2(rawMember, substitution); |
| _suggestProperty( |
| accessor: member as PropertyAccessorElement, |
| referencingInterface: referencingInterface, |
| ); |
| } |
| } |
| } |
| if ((type.isDartCoreFunction && !onlySuper) || |
| type.allSupertypes.any((type) => type.isDartCoreFunction)) { |
| _suggestFunctionCall(); // from builder |
| } |
| // Add members from extensions. Members from extensions accessible in the |
| // same library as the completion location are suggested in this pass. |
| // Members from extensions that are not currently imported are suggested in |
| // the second pass. |
| _addExtensionMembers( |
| type: type, |
| excludedGetters: excludedGetters, |
| includeMethods: includeMethods, |
| includeSetters: includeSetters); |
| _recordOperation(InstanceExtensionMembersOperation( |
| declarationHelper: this, |
| type: type, |
| excludedGetters: excludedGetters, |
| includeMethods: includeMethods, |
| includeSetters: includeSetters)); |
| } |
| |
| /// Adds suggestions for any local declarations that are visible at the |
| /// completion location, given that the completion location is within the |
| /// [node]. |
| /// |
| /// This includes local variables, local functions, parameters, and type |
| /// parameters defined on local functions. |
| /// |
| /// Return the member containing the local declarations that were added, or |
| /// `null` if there is an error such as the AST being malformed or we |
| /// encountered an AST structure that isn't handled correctly. |
| /// |
| /// The returned member can be either a [ClassMember] or a |
| /// [CompilationUnitMember]. |
| AstNode? _addLocalDeclarations(AstNode node) { |
| AstNode? previousNode; |
| AstNode? currentNode = node; |
| while (currentNode != null) { |
| switch (currentNode) { |
| case Block(): |
| _visitStatements(currentNode.statements, previousNode); |
| case CatchClause(): |
| _visitCatchClause(currentNode); |
| case CommentReference(): |
| return _visitCommentReference(currentNode); |
| case ConstructorDeclaration(): |
| _visitParameterList(currentNode.parameters); |
| return currentNode; |
| case DeclaredVariablePattern(): |
| _visitDeclaredVariablePattern(currentNode); |
| case FieldDeclaration(): |
| return currentNode; |
| case ForElement(forLoopParts: var parts): |
| if (parts != previousNode) { |
| _visitForLoopParts(parts); |
| } |
| case ForStatement(forLoopParts: var parts): |
| if (parts != previousNode) { |
| _visitForLoopParts(parts); |
| } |
| case ForPartsWithDeclarations(:var variables): |
| if (variables != previousNode) { |
| _visitForLoopParts(currentNode); |
| } |
| case FunctionDeclaration(:var parent): |
| if (parent is! FunctionDeclarationStatement) { |
| return currentNode; |
| } |
| case FunctionDeclarationStatement(): |
| var functionElement = currentNode.functionDeclaration.declaredElement; |
| if (functionElement != null) { |
| _suggestLocalFunction(functionElement); |
| } |
| case FunctionExpression(): |
| _visitParameterList(currentNode.parameters); |
| _visitTypeParameterList(currentNode.typeParameters); |
| case IfElement(): |
| _visitIfElement(currentNode); |
| case IfStatement(): |
| _visitIfStatement(currentNode); |
| case MethodDeclaration(): |
| _visitParameterList(currentNode.parameters); |
| _visitTypeParameterList(currentNode.typeParameters); |
| return currentNode; |
| case SwitchCase(): |
| _visitStatements(currentNode.statements, previousNode); |
| case SwitchDefault(): |
| _visitStatements(currentNode.statements, previousNode); |
| case SwitchExpressionCase(): |
| _visitSwitchExpressionCase(currentNode); |
| case SwitchPatternCase(): |
| _visitSwitchPatternCase(currentNode, previousNode); |
| case VariableDeclarationList(): |
| _visitVariableDeclarationList(currentNode, previousNode); |
| case CompilationUnit(): |
| case CompilationUnitMember(): |
| return currentNode; |
| } |
| previousNode = currentNode; |
| currentNode = currentNode.parent; |
| } |
| return currentNode; |
| } |
| |
| /// Adds suggestions for any local types that are visible at the completion |
| /// location, given that the completion location is within the [node]. |
| /// |
| /// This includes only type parameters. |
| /// |
| /// Return the member containing the local declarations that were added, or |
| /// `null` if there is an error such as the AST being malformed or we |
| /// encountered an AST structure that isn't handled correctly. |
| /// |
| /// The returned member can be either a [ClassMember] or a |
| /// [CompilationUnitMember]. |
| AstNode? _addLocalTypes(AstNode node) { |
| AstNode? currentNode = node; |
| while (currentNode != null) { |
| switch (currentNode) { |
| case CommentReference(): |
| return currentNode; |
| case ConstructorDeclaration(): |
| _visitParameterList(currentNode.parameters); |
| return currentNode; |
| case FieldDeclaration(): |
| return currentNode; |
| case FunctionDeclaration(:var parent): |
| if (parent is! FunctionDeclarationStatement) { |
| return currentNode; |
| } |
| case FunctionExpression(): |
| _visitTypeParameterList(currentNode.typeParameters); |
| case GenericFunctionType(): |
| _visitTypeParameterList(currentNode.typeParameters); |
| case MethodDeclaration(): |
| _visitTypeParameterList(currentNode.typeParameters); |
| return currentNode; |
| case CompilationUnit(): |
| case CompilationUnitMember(): |
| return currentNode; |
| } |
| currentNode = currentNode.parent; |
| } |
| return currentNode; |
| } |
| |
| /// Adds suggestions for the instance members declared on `Object`. |
| void _addMembersOfDartCoreObject() { |
| _addInstanceMembers( |
| type: request.objectType, |
| excludedGetters: const {}, |
| includeMethods: true, |
| includeSetters: true); |
| } |
| |
| /// Completion is inside the declaration of the [element]. |
| void _addMembersOfEnclosingInstance(InstanceElement element) { |
| var augmented = element.augmented; |
| var referencingInterface = _referencingInterfaceFor(element); |
| |
| for (var accessor in augmented.accessors) { |
| if (!accessor.isSynthetic && (!mustBeStatic || accessor.isStatic)) { |
| _suggestProperty( |
| accessor: accessor, |
| referencingInterface: referencingInterface, |
| ); |
| } |
| } |
| |
| for (var field in augmented.fields) { |
| if (!field.isSynthetic && (!mustBeStatic || field.isStatic)) { |
| _suggestField( |
| field: field, |
| referencingInterface: referencingInterface, |
| ); |
| } |
| } |
| |
| for (var method in augmented.methods) { |
| if (!mustBeStatic || method.isStatic) { |
| _suggestMethod( |
| method: method, |
| referencingInterface: referencingInterface, |
| ); |
| } |
| } |
| var thisType = element.thisType; |
| if (thisType is InterfaceType) { |
| _addExtensionMembers( |
| type: thisType, |
| excludedGetters: {}, |
| includeMethods: true, |
| includeSetters: true); |
| } |
| } |
| |
| /// Completion is inside [declaration]. |
| void _addMembersOfEnclosingNode(CompilationUnitMember declaration) { |
| switch (declaration) { |
| case ClassDeclaration(): |
| var element = declaration.declaredElement; |
| if (element != null) { |
| if (!mustBeType) { |
| _addMembersOfEnclosingInstance(element); |
| } |
| _suggestTypeParameters(element.typeParameters); |
| } |
| case ClassTypeAlias(): |
| var element = declaration.declaredElement; |
| if (element != null) { |
| _suggestTypeParameters(element.typeParameters); |
| } |
| case EnumDeclaration(): |
| var element = declaration.declaredElement; |
| if (element != null) { |
| if (!mustBeType) { |
| _addMembersOfEnclosingInstance(element); |
| } |
| _suggestTypeParameters(element.typeParameters); |
| } |
| case ExtensionDeclaration(): |
| var element = declaration.declaredElement; |
| if (element != null) { |
| if (!mustBeType) { |
| _addMembersOfEnclosingInstance(element); |
| } |
| _suggestTypeParameters(element.typeParameters); |
| } |
| case ExtensionTypeDeclaration(): |
| var element = declaration.declaredElement; |
| if (element != null) { |
| if (!mustBeType) { |
| _addMembersOfEnclosingInstance(element); |
| var fieldElement = declaration.representation.fieldElement; |
| if (fieldElement != null) { |
| _suggestField(field: fieldElement); |
| } |
| } |
| _suggestTypeParameters(element.typeParameters); |
| } |
| case FunctionTypeAlias(): |
| var element = declaration.declaredElement; |
| if (element != null) { |
| _suggestTypeParameters(element.typeParameters); |
| } |
| case GenericTypeAlias(): |
| var element = declaration.declaredElement; |
| if (element is TypeAliasElement) { |
| _suggestTypeParameters(element.typeParameters); |
| } |
| case MixinDeclaration(): |
| var element = declaration.declaredElement; |
| if (element != null) { |
| if (!mustBeType) { |
| _addMembersOfEnclosingInstance(element); |
| } |
| _suggestTypeParameters(element.typeParameters); |
| } |
| } |
| } |
| |
| /// Add the static [accessors], [constructors], [fields], and [methods] |
| /// defined by the [containingElement]. |
| void _addStaticMembers( |
| {required List<PropertyAccessorElement> accessors, |
| required List<ConstructorElement> constructors, |
| required Element containingElement, |
| required List<FieldElement> fields, |
| required List<MethodElement> methods}) { |
| for (var accessor in accessors) { |
| if (accessor.isStatic && |
| !accessor.isSynthetic && |
| accessor.isVisibleIn(request.libraryElement)) { |
| _suggestProperty(accessor: accessor); |
| } |
| } |
| for (var field in fields) { |
| if (field.isStatic && |
| (!field.isSynthetic || |
| (containingElement is EnumElement && field.name == 'values')) && |
| field.isVisibleIn(request.libraryElement)) { |
| if (field.isEnumConstant) { |
| var enumElement = field.enclosingElement; |
| var matcherScore = |
| state.matcher.score('${enumElement.name}.${field.name}'); |
| if (matcherScore != -1) { |
| var suggestion = EnumConstantSuggestion( |
| importData: null, |
| element: field, |
| includeEnumName: false, |
| matcherScore: matcherScore); |
| collector.addSuggestion(suggestion); |
| } |
| } else { |
| _suggestField(field: field); |
| } |
| } |
| } |
| if (!mustBeAssignable) { |
| var allowNonFactory = |
| containingElement is ClassElement && !containingElement.isAbstract; |
| for (var constructor in constructors) { |
| if (constructor.isVisibleIn(request.libraryElement) && |
| (allowNonFactory || constructor.isFactory)) { |
| _suggestConstructor( |
| constructor, |
| hasClassName: true, |
| importData: null, |
| isConstructorRedirect: false, |
| ); |
| } |
| } |
| for (var method in methods) { |
| if (method.isStatic && method.isVisibleIn(request.libraryElement)) { |
| _suggestMethod(method: method); |
| } |
| } |
| } |
| } |
| |
| /// Adds suggestions for any top-level declarations that are visible within |
| /// the [library]. |
| /// |
| /// The [library] is the library in which completion is being requested. |
| void _addTopLevelDeclarations(LibraryElement library) { |
| for (var unit in library.units) { |
| for (var element in unit.classes) { |
| _suggestClass(element, null); |
| } |
| for (var element in unit.enums) { |
| _suggestEnum(element, null); |
| } |
| // TODO(brianwilkerson): This should suggest extensions that have static |
| // members. We appear to not have any tests for this case. |
| for (var element in unit.extensionTypes) { |
| _suggestExtensionType(element, null); |
| } |
| for (var element in unit.mixins) { |
| _suggestMixin(element, null); |
| } |
| for (var element in unit.typeAliases) { |
| _suggestTypeAlias(element, null); |
| } |
| if (!mustBeType) { |
| for (var element in unit.accessors) { |
| if (!element.isSynthetic) { |
| if (element.isGetter || element.correspondingGetter == null) { |
| _suggestTopLevelProperty(element, null); |
| } |
| } |
| } |
| for (var element in unit.extensions) { |
| if (element.name != null) { |
| _suggestExtension(element, null); |
| } |
| } |
| for (var element in unit.functions) { |
| _suggestTopLevelFunction(element, null); |
| } |
| for (var element in unit.topLevelVariables) { |
| if (!element.isSynthetic) { |
| _suggestTopLevelVariable(element, null); |
| } |
| } |
| } |
| } |
| } |
| |
| bool _canAccessInstanceMember(ExecutableElement element) { |
| if (element.isStatic) { |
| return false; |
| } |
| |
| var requestLibrary = request.libraryElement; |
| if (!element.isAccessibleIn(requestLibrary)) { |
| return false; |
| } |
| |
| if (element.isInternal) { |
| switch (request.fileState.workspacePackage) { |
| case PubPackage pubPackage: |
| if (!pubPackage.contains(element.librarySource)) { |
| return false; |
| } |
| } |
| } |
| |
| if (element.isProtected) { |
| var elementInterface = element.enclosingElement; |
| if (elementInterface is! InterfaceElement) { |
| return false; |
| } |
| |
| if (elementInterface.library != requestLibrary) { |
| var contextInterface = request.target.enclosingInterfaceElement; |
| if (contextInterface == null) { |
| return false; |
| } |
| |
| var contextType = contextInterface.thisType; |
| if (contextType.asInstanceOf(elementInterface) == null) { |
| return false; |
| } |
| } |
| } |
| |
| if (element.isVisibleForTesting) { |
| if (element.library != requestLibrary) { |
| var fileState = request.fileState; |
| switch (fileState.workspacePackage) { |
| case PubPackage pubPackage: |
| // Must be in the same package. |
| if (!pubPackage.contains(element.librarySource)) { |
| return false; |
| } |
| // Must be in the `test` directory. |
| if (!pubPackage.isInTestDirectory(fileState.resource)) { |
| return false; |
| } |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| /// Returns `true` if the [identifier] is composed of one or more underscore |
| /// characters and nothing else. |
| bool _isUnused(String identifier) => UnusedIdentifier.hasMatch(identifier); |
| |
| /// Record that the given [operation] should be performed in the second pass. |
| void _recordOperation(NotImportedOperation operation) { |
| notImportedOperations.add(operation); |
| } |
| |
| /// Returns the interface element for the type of `this` within the |
| /// declaration of the given class-like [element]. |
| InterfaceElement? _referencingInterfaceFor(Element element) { |
| if (element is InterfaceElement) { |
| return element; |
| } else if (element is InstanceElement) { |
| var thisElement = element.thisType.element; |
| if (thisElement is InterfaceElement) { |
| return thisElement; |
| } |
| } |
| return null; |
| } |
| |
| /// Adds a suggestion for the class represented by the [element]. The [prefix] |
| /// is the prefix by which the element is imported. |
| void _suggestClass(ClassElement element, ImportData? importData) { |
| if (visibilityTracker.isVisible(element: element, importData: importData)) { |
| if ((mustBeExtendable && |
| !element.isExtendableIn(request.libraryElement)) || |
| (mustBeImplementable && |
| !element.isImplementableIn(request.libraryElement)) || |
| (mustBeMixable && !element.isMixableIn(request.libraryElement))) { |
| return; |
| } |
| if (!(mustBeConstant && !objectPatternAllowed) && !excludeTypeNames) { |
| var matcherScore = state.matcher.score(element.displayName); |
| if (matcherScore != -1) { |
| var suggestion = ClassSuggestion( |
| importData: importData, |
| element: element, |
| matcherScore: matcherScore); |
| collector.addSuggestion(suggestion); |
| } |
| } |
| if (!mustBeType) { |
| var augmented = element.augmented; |
| _suggestStaticFields(augmented.fields, importData); |
| _suggestConstructors(augmented.constructors, importData, |
| allowNonFactory: !element.isAbstract); |
| } |
| } |
| } |
| |
| /// Adds a suggestion for the constructor represented by the [element]. The |
| /// [prefix] is the prefix by which the class is imported. |
| void _suggestConstructor( |
| ConstructorElement element, { |
| required ImportData? importData, |
| required bool hasClassName, |
| required bool isConstructorRedirect, |
| }) { |
| if (mustBeAssignable) { |
| return; |
| } |
| |
| if (!element.isVisibleIn(request.libraryElement)) { |
| return; |
| } |
| if (importData?.isNotImported ?? false) { |
| if (!visibilityTracker.isVisible( |
| element: element.enclosingElement, importData: importData)) { |
| // If the constructor is on a class from a not-yet-imported library and |
| // the class isn't visible, then we shouldn't suggest it. |
| // |
| // We could consider computing a prefix and updating the [importData] in |
| // order to avoid the collision, but we don't currently do that for any |
| // not-yet-imported elements (nor for imported elements that are |
| // shadowed by local declarations). |
| return; |
| } |
| } else { |
| // Add the class to the visibility tracker so that we will know later that |
| // any non-imported elements with the same name are not visible. |
| visibilityTracker.isVisible( |
| element: element.enclosingElement, importData: importData); |
| } |
| |
| // TODO(keertip): Compute the completion string. |
| var matcherScore = state.matcher.score(element.displayName); |
| if (matcherScore != -1) { |
| var isTearOff = |
| preferNonInvocation || (mustBeConstant && !element.isConst); |
| |
| var suggestion = ConstructorSuggestion( |
| importData: importData, |
| element: element, |
| hasClassName: hasClassName, |
| isTearOff: isTearOff, |
| isRedirect: isConstructorRedirect, |
| suggestUnnamedAsNew: suggestUnnamedAsNew || isTearOff, |
| matcherScore: matcherScore, |
| ); |
| collector.addSuggestion(suggestion); |
| } |
| } |
| |
| /// Adds a suggestion for each of the [constructors]. |
| void _suggestConstructors( |
| List<ConstructorElement> constructors, ImportData? importData, |
| {bool allowNonFactory = true}) { |
| if (mustBeAssignable) { |
| return; |
| } |
| for (var constructor in constructors) { |
| if (constructor.isVisibleIn(request.libraryElement) && |
| (allowNonFactory || constructor.isFactory)) { |
| _suggestConstructor( |
| constructor, |
| hasClassName: false, |
| importData: importData, |
| isConstructorRedirect: false, |
| ); |
| } |
| } |
| } |
| |
| /// Adds a suggestion for the enum represented by the [element]. The [prefix] |
| /// is the prefix by which the element is imported. |
| void _suggestEnum(EnumElement element, ImportData? importData) { |
| if (visibilityTracker.isVisible(element: element, importData: importData)) { |
| if (mustBeExtendable || mustBeImplementable || mustBeMixable) { |
| return; |
| } |
| var matcherScore = state.matcher.score(element.displayName); |
| if (matcherScore != -1) { |
| var suggestion = EnumSuggestion( |
| importData: importData, |
| element: element, |
| matcherScore: matcherScore); |
| collector.addSuggestion(suggestion); |
| } |
| |
| if (!mustBeType) { |
| var augmented = element.augmented; |
| _suggestStaticFields(augmented.fields, importData); |
| _suggestConstructors(augmented.constructors, importData, |
| allowNonFactory: false); |
| } |
| } |
| } |
| |
| /// Adds a suggestion for the extension represented by the [element]. The |
| /// [prefix] is the prefix by which the element is imported. |
| void _suggestExtension(ExtensionElement element, ImportData? importData) { |
| if (visibilityTracker.isVisible(element: element, importData: importData)) { |
| if (mustBeExtendable || mustBeImplementable || mustBeMixable) { |
| return; |
| } |
| var matcherScore = state.matcher.score(element.displayName); |
| if (matcherScore != -1) { |
| var suggestion = ExtensionSuggestion( |
| importData: importData, |
| element: element, |
| matcherScore: matcherScore); |
| collector.addSuggestion(suggestion); |
| } |
| if (!mustBeType) { |
| var augmented = element.augmented; |
| _suggestStaticFields(augmented.fields, importData); |
| } |
| } |
| } |
| |
| /// Adds a suggestion for the extension type represented by the [element]. The |
| /// [prefix] is the prefix by which the element is imported. |
| void _suggestExtensionType( |
| ExtensionTypeElement element, ImportData? importData) { |
| if (visibilityTracker.isVisible(element: element, importData: importData)) { |
| if (mustBeExtendable || mustBeImplementable || mustBeMixable) { |
| return; |
| } |
| var matcherScore = state.matcher.score(element.displayName); |
| if (matcherScore != -1) { |
| var suggestion = ExtensionTypeSuggestion( |
| importData: importData, |
| element: element, |
| matcherScore: matcherScore); |
| collector.addSuggestion(suggestion); |
| } |
| if (!mustBeType) { |
| var augmented = element.augmented; |
| _suggestStaticFields(augmented.fields, importData); |
| _suggestConstructors(augmented.constructors, importData); |
| } |
| } |
| } |
| |
| /// Adds a suggestion for the [field]. |
| /// |
| /// The [referencingInterface] is used to compute the inheritance distance to |
| /// an instance member, and should not be provided for static members. If a |
| /// [referencingInterface] is provided, it should be the class in which |
| /// completion was requested. |
| void _suggestField( |
| {required FieldElement field, InterfaceElement? referencingInterface}) { |
| if (visibilityTracker.isVisible(element: field, importData: null)) { |
| if ((mustBeAssignable && field.setter == null) || |
| (mustBeConstant && !field.isConst)) { |
| return; |
| } |
| var matcherScore = state.matcher.score(field.displayName); |
| if (matcherScore != -1) { |
| var suggestion = FieldSuggestion( |
| element: field, |
| matcherScore: matcherScore, |
| referencingInterface: referencingInterface); |
| collector.addSuggestion(suggestion); |
| } |
| } |
| } |
| |
| /// Adds a suggestion for the method `call` defined on the class `Function`. |
| void _suggestFunctionCall() { |
| var matcherScore = state.matcher.score('call'); |
| if (matcherScore != -1) { |
| collector.addSuggestion(FunctionCall(matcherScore: matcherScore)); |
| } |
| } |
| |
| /// Adds a suggestion for the local function represented by the [element]. |
| void _suggestLocalFunction(ExecutableElement element) { |
| if (element is FunctionElement && |
| visibilityTracker.isVisible(element: element, importData: null)) { |
| if (mustBeAssignable || |
| mustBeConstant || |
| (mustBeNonVoid && element.returnType is VoidType)) { |
| return; |
| } |
| var matcherScore = state.matcher.score(element.displayName); |
| if (matcherScore != -1) { |
| var suggestion = LocalFunctionSuggestion( |
| kind: _executableSuggestionKind, |
| element: element, |
| matcherScore: matcherScore); |
| collector.addSuggestion(suggestion); |
| } |
| } |
| } |
| |
| /// Adds a suggestion for the [method]. |
| /// |
| /// If [ignoreVisibility] is `true` then the visibility tracker will not be |
| /// used to determine whether the element is shadowed. This should be used |
| /// when suggesting a member accessed through a target. |
| /// |
| /// The [referencingInterface] is used to compute the inheritance distance to |
| /// an instance member, and should not be provided for static members. If a |
| /// [referencingInterface] is provided, it should be the class in which |
| /// completion was requested. |
| void _suggestMethod( |
| {required MethodElement method, |
| bool ignoreVisibility = false, |
| ImportData? importData, |
| InterfaceElement? referencingInterface}) { |
| if (ignoreVisibility || |
| visibilityTracker.isVisible(element: method, importData: importData)) { |
| if (mustBeAssignable || |
| mustBeConstant || |
| (mustBeNonVoid && method.returnType is VoidType)) { |
| return; |
| } |
| var matcherScore = state.matcher.score(method.displayName); |
| if (matcherScore != -1) { |
| var suggestion = MethodSuggestion( |
| kind: _executableSuggestionKind, |
| element: method, |
| importData: importData, |
| matcherScore: matcherScore, |
| referencingInterface: referencingInterface); |
| collector.addSuggestion(suggestion); |
| } |
| } |
| } |
| |
| /// Adds a suggestion for the mixin represented by the [element]. The [prefix] |
| /// is the prefix by which the element is imported. |
| void _suggestMixin(MixinElement element, ImportData? importData) { |
| if (visibilityTracker.isVisible(element: element, importData: importData)) { |
| if (mustBeExtendable || |
| (mustBeImplementable && |
| !element.isImplementableIn(request.libraryElement))) { |
| return; |
| } |
| var matcherScore = state.matcher.score(element.displayName); |
| if (matcherScore != -1) { |
| var suggestion = MixinSuggestion( |
| importData: importData, |
| element: element, |
| matcherScore: matcherScore); |
| collector.addSuggestion(suggestion); |
| } |
| if (!mustBeType) { |
| var augmented = element.augmented; |
| _suggestStaticFields(augmented.fields, importData); |
| } |
| } |
| } |
| |
| /// Adds a suggestion for the parameter represented by the [element]. |
| void _suggestParameter(ParameterElement element) { |
| if (visibilityTracker.isVisible(element: element, importData: null)) { |
| if (mustBeConstant || _isUnused(element.name)) { |
| return; |
| } |
| var matcherScore = state.matcher.score(element.displayName); |
| if (matcherScore != -1) { |
| var suggestion = FormalParameterSuggestion( |
| element: element, |
| distance: _variableDistance++, |
| matcherScore: matcherScore, |
| ); |
| collector.addSuggestion(suggestion); |
| } |
| } |
| } |
| |
| /// Adds a suggestion for the getter or setter represented by the [accessor]. |
| /// |
| /// If [ignoreVisibility] is `true` then the visibility tracker will not be |
| /// used to determine whether the element is shadowed. This should be used |
| /// when suggesting a member accessed through a target. |
| /// |
| /// The [referencingInterface] is used to compute the inheritance distance to |
| /// an instance member, and should not be provided for static members. If a |
| /// [referencingInterface] is provided, it should be the class in which |
| /// completion was requested. |
| void _suggestProperty( |
| {required PropertyAccessorElement accessor, |
| bool ignoreVisibility = false, |
| ImportData? importData, |
| InterfaceElement? referencingInterface}) { |
| if (ignoreVisibility || |
| visibilityTracker.isVisible( |
| element: accessor, importData: importData)) { |
| if ((mustBeAssignable && |
| accessor.isGetter && |
| accessor.correspondingSetter == null) || |
| mustBeConstant || |
| (mustBeNonVoid && accessor.returnType is VoidType)) { |
| return; |
| } |
| var matcherScore = state.matcher.score(accessor.displayName); |
| if (matcherScore != -1) { |
| var suggestion = PropertyAccessSuggestion( |
| element: accessor, |
| importData: importData, |
| matcherScore: matcherScore, |
| referencingInterface: referencingInterface); |
| collector.addSuggestion(suggestion); |
| } |
| } |
| } |
| |
| /// Adds a suggestion for the record type [field] with the given [name]. |
| void _suggestRecordField( |
| {required RecordTypeField field, required String name}) { |
| var matcherScore = state.matcher.score(name); |
| if (matcherScore != -1) { |
| collector.addSuggestion(RecordFieldSuggestion( |
| field: field, name: name, matcherScore: matcherScore)); |
| } |
| } |
| |
| /// Adds a suggestion for the enum constant represented by the [element]. |
| /// The [importData] should be provided if the enum is imported. |
| void _suggestStaticField(FieldElement element, ImportData? importData) { |
| if (!element.isStatic || |
| (mustBeAssignable && !(element.isFinal || element.isConst)) || |
| (mustBeConstant && !element.isConst)) { |
| return; |
| } |
| var contextType = request.contextType; |
| if (contextType != null && |
| request.libraryElement.typeSystem |
| .isSubtypeOf(element.type, contextType)) { |
| if (element.isEnumConstant) { |
| var enumElement = element.enclosingElement; |
| var matcherScore = state.matcher |
| .score('${enumElement.displayName}.${element.displayName}'); |
| if (matcherScore != -1) { |
| var suggestion = EnumConstantSuggestion( |
| importData: importData, |
| element: element, |
| matcherScore: matcherScore); |
| collector.addSuggestion(suggestion); |
| } |
| } else { |
| var matcherScore = state.matcher.score(element.displayName); |
| if (matcherScore != -1) { |
| var suggestion = StaticFieldSuggestion( |
| importData: importData, |
| element: element, |
| matcherScore: matcherScore); |
| collector.addSuggestion(suggestion); |
| } |
| } |
| } |
| } |
| |
| /// Adds a suggestion for each of the static fields in the list of [fields]. |
| void _suggestStaticFields(List<FieldElement> fields, ImportData? importData) { |
| for (var field in fields) { |
| if (field.isVisibleIn(request.libraryElement)) { |
| _suggestStaticField(field, importData); |
| } |
| } |
| } |
| |
| /// Adds a suggestion for a parameter that is in the super constructor. |
| void _suggestSuperParameter(ParameterElement element) { |
| var matcherScore = state.matcher.score(element.displayName); |
| if (matcherScore != -1) { |
| collector.addSuggestion(SuperParameterSuggestion( |
| element: element, matcherScore: matcherScore)); |
| } |
| } |
| |
| /// Adds a suggestion for the function represented by the [element]. The |
| /// [prefix] is the prefix by which the element is imported. |
| void _suggestTopLevelFunction( |
| FunctionElement element, ImportData? importData) { |
| if (visibilityTracker.isVisible(element: element, importData: importData)) { |
| if (mustBeAssignable || |
| mustBeConstant || |
| (mustBeNonVoid && element.returnType is VoidType) || |
| mustBeType) { |
| return; |
| } |
| var matcherScore = state.matcher.score(element.displayName); |
| if (matcherScore != -1) { |
| var suggestion = TopLevelFunctionSuggestion( |
| importData: importData, |
| element: element, |
| matcherScore: matcherScore, |
| kind: _executableSuggestionKind); |
| collector.addSuggestion(suggestion); |
| } |
| } |
| } |
| |
| /// Adds a suggestion for the getter or setter represented by the [element]. |
| /// The [prefix] is the prefix by which the element is imported. |
| void _suggestTopLevelProperty( |
| PropertyAccessorElement element, ImportData? importData) { |
| if (visibilityTracker.isVisible(element: element, importData: importData)) { |
| if ((mustBeAssignable && |
| element.isGetter && |
| element.correspondingSetter == null) || |
| (mustBeConstant && !element.isConst) || |
| (mustBeNonVoid && element.returnType is VoidType) || |
| mustBeType) { |
| return; |
| } |
| var matcherScore = state.matcher.score(element.displayName); |
| if (matcherScore != -1) { |
| var suggestion = TopLevelPropertyAccessSuggestion( |
| importData: importData, |
| element: element, |
| matcherScore: matcherScore); |
| collector.addSuggestion(suggestion); |
| } |
| } |
| } |
| |
| /// Adds a suggestion for the getter or setter represented by the [element]. |
| /// The [prefix] is the prefix by which the element is imported. |
| void _suggestTopLevelVariable( |
| TopLevelVariableElement element, ImportData? importData) { |
| if (visibilityTracker.isVisible(element: element, importData: importData)) { |
| if ((mustBeAssignable && element.setter == null) || |
| mustBeConstant && !element.isConst || |
| mustBeType) { |
| return; |
| } |
| var matcherScore = state.matcher.score(element.displayName); |
| if (matcherScore != -1) { |
| var suggestion = TopLevelVariableSuggestion( |
| importData: importData, |
| element: element, |
| matcherScore: matcherScore); |
| collector.addSuggestion(suggestion); |
| } |
| } |
| } |
| |
| /// Adds a suggestion for the type alias represented by the [element]. The |
| /// [prefix] is the prefix by which the element is imported. |
| void _suggestTypeAlias(TypeAliasElement element, ImportData? importData) { |
| if (visibilityTracker.isVisible(element: element, importData: importData)) { |
| var matcherScore = state.matcher.score(element.displayName); |
| if (matcherScore != -1) { |
| var suggestion = TypeAliasSuggestion( |
| importData: importData, |
| element: element, |
| matcherScore: matcherScore); |
| collector.addSuggestion(suggestion); |
| } |
| if (!mustBeType) { |
| _addConstructorsForAliasedElement(element, importData); |
| } |
| } |
| } |
| |
| /// Adds a suggestion for the type parameter represented by the [element]. |
| void _suggestTypeParameter(TypeParameterElement element) { |
| if (visibilityTracker.isVisible(element: element, importData: null)) { |
| var matcherScore = state.matcher.score(element.displayName); |
| if (matcherScore != -1) { |
| var suggestion = TypeParameterSuggestion( |
| element: element, matcherScore: matcherScore); |
| collector.addSuggestion(suggestion); |
| } |
| } |
| } |
| |
| /// Adds a suggestion for each of the [typeParameters]. |
| void _suggestTypeParameters(List<TypeParameterElement> typeParameters) { |
| for (var parameter in typeParameters) { |
| _suggestTypeParameter(parameter); |
| } |
| } |
| |
| /// Adds a suggestion for the local variable represented by the [element]. |
| void _suggestVariable(LocalVariableElement element) { |
| if (visibilityTracker.isVisible(element: element, importData: null)) { |
| if (mustBeConstant && !element.isConst) { |
| return; |
| } |
| var matcherScore = state.matcher.score(element.displayName); |
| if (matcherScore != -1) { |
| var suggestion = LocalVariableSuggestion( |
| element: element, |
| distance: _variableDistance++, |
| matcherScore: matcherScore); |
| collector.addSuggestion(suggestion); |
| } |
| } |
| } |
| |
| void _visitCatchClause(CatchClause node) { |
| var exceptionElement = node.exceptionParameter?.declaredElement; |
| if (exceptionElement != null) { |
| _suggestVariable(exceptionElement); |
| } |
| |
| var stackTraceElement = node.stackTraceParameter?.declaredElement; |
| if (stackTraceElement != null) { |
| _suggestVariable(stackTraceElement); |
| } |
| } |
| |
| AstNode? _visitCommentReference(CommentReference node) { |
| var comment = node.parent; |
| var member = comment?.parent; |
| switch (member) { |
| case ConstructorDeclaration(): |
| _visitParameterList(member.parameters); |
| case FunctionDeclaration(): |
| var functionExpression = member.functionExpression; |
| _visitParameterList(functionExpression.parameters); |
| _visitTypeParameterList(functionExpression.typeParameters); |
| case FunctionExpression(): |
| _visitParameterList(member.parameters); |
| _visitTypeParameterList(member.typeParameters); |
| case MethodDeclaration(): |
| _visitParameterList(member.parameters); |
| _visitTypeParameterList(member.typeParameters); |
| } |
| return comment; |
| } |
| |
| void _visitDeclaredVariablePattern(DeclaredVariablePattern pattern) { |
| var declaredElement = pattern.declaredElement; |
| if (declaredElement != null) { |
| _suggestVariable(declaredElement); |
| } |
| } |
| |
| void _visitForLoopParts(ForLoopParts node) { |
| if (node is ForEachPartsWithDeclaration) { |
| var declaredElement = node.loopVariable.declaredElement; |
| if (declaredElement != null) { |
| _suggestVariable(declaredElement); |
| } |
| } else if (node is ForEachPartsWithPattern) { |
| _visitPattern(node.pattern); |
| } else if (node is ForPartsWithDeclarations) { |
| var variables = node.variables; |
| for (var variable in variables.variables) { |
| var declaredElement = variable.declaredElement; |
| if (declaredElement is LocalVariableElement) { |
| _suggestVariable(declaredElement); |
| } |
| } |
| } else if (node is ForPartsWithPattern) { |
| _visitPattern(node.variables.pattern); |
| } |
| } |
| |
| void _visitIfElement(IfElement node) { |
| var elseKeyword = node.elseKeyword; |
| if (elseKeyword == null || offset < elseKeyword.offset) { |
| var pattern = node.caseClause?.guardedPattern.pattern; |
| if (pattern != null) { |
| _visitPattern(pattern); |
| } |
| } |
| } |
| |
| void _visitIfStatement(IfStatement node) { |
| var elseKeyword = node.elseKeyword; |
| if (elseKeyword == null || offset < elseKeyword.offset) { |
| var pattern = node.caseClause?.guardedPattern.pattern; |
| if (pattern != null) { |
| _visitPattern(pattern); |
| } |
| } |
| } |
| |
| void _visitParameterList(FormalParameterList? parameterList) { |
| if (parameterList != null) { |
| for (var param in parameterList.parameters) { |
| var declaredElement = param.declaredElement; |
| if (declaredElement != null) { |
| _suggestParameter(declaredElement); |
| } |
| } |
| } |
| } |
| |
| void _visitPattern(DartPattern pattern) { |
| switch (pattern) { |
| case CastPattern(:var pattern): |
| _visitPattern(pattern); |
| case DeclaredVariablePattern(): |
| _visitDeclaredVariablePattern(pattern); |
| case ListPattern(): |
| for (var element in pattern.elements) { |
| if (element is DartPattern) { |
| _visitPattern(element); |
| } else if (element is RestPatternElement) { |
| var elementPattern = element.pattern; |
| if (elementPattern != null) { |
| _visitPattern(elementPattern); |
| } |
| } |
| } |
| case LogicalAndPattern(): |
| _visitPattern(pattern.leftOperand); |
| _visitPattern(pattern.rightOperand); |
| case LogicalOrPattern(): |
| _visitPattern(pattern.leftOperand); |
| _visitPattern(pattern.rightOperand); |
| case MapPattern(): |
| for (var element in pattern.elements) { |
| if (element is MapPatternEntry) { |
| _visitPattern(element.value); |
| } else if (element is RestPatternElement) { |
| var elementPattern = element.pattern; |
| if (elementPattern != null) { |
| _visitPattern(elementPattern); |
| } |
| } |
| } |
| case NullAssertPattern(): |
| _visitPattern(pattern.pattern); |
| case NullCheckPattern(): |
| _visitPattern(pattern.pattern); |
| case ObjectPattern(): |
| for (var field in pattern.fields) { |
| _visitPattern(field.pattern); |
| } |
| case ParenthesizedPattern(): |
| _visitPattern(pattern.pattern); |
| case RecordPattern(): |
| for (var field in pattern.fields) { |
| _visitPattern(field.pattern); |
| } |
| case _: |
| // Do nothing |
| } |
| } |
| |
| void _visitStatements(NodeList<Statement> statements, AstNode? child) { |
| // Visit the statements in reverse order so that shadowing declarations are |
| // found before the declarations they shadow. |
| for (var i = statements.length - 1; i >= 0; i--) { |
| var statement = statements[i]; |
| if (statement == child) { |
| // Skip the child that was passed in because we will have already |
| // visited it and don't want to suggest declared variables twice. |
| continue; |
| } |
| // TODO(brianwilkerson): I think we need to compare to the end of the |
| // statement for variable declarations and the offset for functions. |
| if (statement.offset < offset) { |
| if (statement is VariableDeclarationStatement) { |
| var variables = statement.variables; |
| for (var variable in variables.variables) { |
| if (variable.end < offset) { |
| var declaredElement = variable.declaredElement; |
| if (declaredElement is LocalVariableElement) { |
| _suggestVariable(declaredElement); |
| } |
| } |
| } |
| } else if (statement is FunctionDeclarationStatement) { |
| var declaration = statement.functionDeclaration; |
| if (declaration.offset < offset) { |
| var name = declaration.name.lexeme; |
| if (name.isNotEmpty) { |
| var declaredElement = declaration.declaredElement; |
| if (declaredElement != null) { |
| _suggestLocalFunction(declaredElement); |
| } |
| } |
| } |
| } else if (statement is PatternVariableDeclarationStatement) { |
| var declaration = statement.declaration; |
| if (declaration.end < offset) { |
| _visitPattern(declaration.pattern); |
| } |
| } |
| } |
| } |
| } |
| |
| void _visitSwitchExpressionCase(SwitchExpressionCase node) { |
| if (offset >= node.arrow.end) { |
| _visitPattern(node.guardedPattern.pattern); |
| } |
| } |
| |
| void _visitSwitchPatternCase(SwitchPatternCase node, AstNode? child) { |
| if (offset >= node.colon.end) { |
| _visitStatements(node.statements, child); |
| _visitPattern(node.guardedPattern.pattern); |
| var parent = node.parent; |
| if (parent is SwitchStatement) { |
| var members = parent.members; |
| var index = members.indexOf(node) - 1; |
| while (index >= 0) { |
| var member = members[index]; |
| if (member is SwitchPatternCase && member.statements.isEmpty) { |
| _visitPattern(member.guardedPattern.pattern); |
| } else { |
| break; |
| } |
| index--; |
| } |
| } |
| } |
| } |
| |
| void _visitTypeParameterList(TypeParameterList? typeParameters) { |
| if (typeParameters == null) { |
| return; |
| } |
| |
| if (excludedNodes.contains(typeParameters)) { |
| return; |
| } |
| |
| for (var typeParameter in typeParameters.typeParameters) { |
| var element = typeParameter.declaredElement; |
| if (element != null) { |
| _suggestTypeParameter(element); |
| } |
| } |
| } |
| |
| void _visitVariableDeclarationList( |
| VariableDeclarationList node, AstNode? child) { |
| var variables = node.variables; |
| if (child is VariableDeclaration) { |
| var index = variables.indexOf(child); |
| for (var i = index - 1; i >= 0; i--) { |
| var element = variables[i].declaredElement; |
| if (element is LocalVariableElement) { |
| _suggestVariable(element); |
| } |
| } |
| } |
| } |
| } |
| |
| extension on Element { |
| /// Whether this element is visible within the [referencingLibrary]. |
| /// |
| /// An element is visible if it's declared in the [referencingLibrary] or if |
| /// the name is not private. |
| bool isVisibleIn(LibraryElement referencingLibrary) { |
| if (library == referencingLibrary) { |
| return true; |
| } |
| var name = this.name; |
| return name != null && !Identifier.isPrivateName(name); |
| } |
| } |
| |
| extension on List<ExecutableElement> { |
| /// Returns the element in this list that is the best element to suggest. |
| /// |
| /// Getters are preferred over setters, otherwise the first element in the |
| /// list is returned under the assumption that it's lower in the hierarchy. |
| ExecutableElement get bestMember { |
| ExecutableElement bestMember = this[0]; |
| if (bestMember is PropertyAccessorElement && bestMember.isSetter) { |
| for (var i = 1; i < length; i++) { |
| var member = this[i]; |
| if (member is PropertyAccessorElement && member.isGetter) { |
| return member; |
| } |
| } |
| } |
| return bestMember; |
| } |
| } |
| |
| extension on PropertyAccessorElement { |
| /// Whether this accessor is an accessor for a constant variable. |
| bool get isConst { |
| if (isSynthetic) { |
| if (variable2 case var variable?) { |
| return variable.isConst; |
| } |
| } |
| return false; |
| } |
| } |