blob: 7ec4b9e682a1742ad21e387eb9c71e8a67e96bc9 [file] [log] [blame]
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analysis_server/src/protocol_server.dart'
show CompletionSuggestionKind;
import 'package:analysis_server/src/services/completion/dart/completion_manager.dart';
import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart'
show SuggestionBuilder;
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/visitor2.dart';
import 'package:analyzer_plugin/src/utilities/completion/optype.dart';
/// A visitor for building suggestions based upon the elements defined by
/// a source file contained in the same library but not the same as
/// the source in which the completions are being requested.
class LibraryElementSuggestionBuilder
extends GeneralizingElementVisitor2<void> {
final DartCompletionRequest request;
final SuggestionBuilder builder;
final OpType opType;
CompletionSuggestionKind kind;
final String? prefix;
/// The set of libraries that have been, or are currently being, visited.
final Set<LibraryElement> visitedLibraries = <LibraryElement>{};
factory LibraryElementSuggestionBuilder(
DartCompletionRequest request,
SuggestionBuilder builder, [
String? prefix,
]) {
var opType = request.opType;
var kind =
request.target.isFunctionalArgument()
? CompletionSuggestionKind.IDENTIFIER
: opType.suggestKind;
return LibraryElementSuggestionBuilder._(
request,
builder,
opType,
kind,
prefix,
);
}
LibraryElementSuggestionBuilder._(
this.request,
this.builder,
this.opType,
this.kind,
this.prefix,
);
@override
void visitClassElement(ClassElement element) {
AstNode node = request.target.containingNode;
var libraryElement = request.libraryElement;
if (node is ExtendsClause && !element.isExtendableIn2(libraryElement)) {
return;
} else if (node is ImplementsClause &&
!element.isImplementableIn2(libraryElement)) {
return;
} else if (node is WithClause && !element.isMixableIn2(libraryElement)) {
return;
}
_visitInterfaceElement(element);
}
@override
void visitElement(Element element) {
// ignored
}
@override
visitEnumElement(EnumElement element) {
_visitInterfaceElement(element);
}
@override
void visitExtensionElement(ExtensionElement element) {
if (opType.includeReturnValueSuggestions) {
if (element.name3 != null) {
builder.suggestExtension(element, kind: kind, prefix: prefix);
}
}
}
@override
void visitExtensionTypeElement(ExtensionTypeElement element) {
_visitInterfaceElement(element);
}
@override
void visitGetterElement(GetterElement element) {
var variable = element.variable3;
if (opType.includeReturnValueSuggestions ||
(opType.includeAnnotationSuggestions &&
variable != null &&
variable.isConst)) {
var parent = element.enclosingElement2;
if (parent is InterfaceElement || parent is ExtensionElement) {
if (element.isSynthetic) {
if (variable is FieldElement) {
builder.suggestField(variable, inheritanceDistance: 0.0);
}
} else {
builder.suggestGetter(element, inheritanceDistance: 0.0);
}
} else {
builder.suggestTopLevelGetter(element, prefix: prefix);
}
}
}
@override
void visitLibraryElement(LibraryElement element) {
if (visitedLibraries.add(element)) {
element.visitChildren2(this);
}
}
@override
visitMixinElement(MixinElement element) {
AstNode node = request.target.containingNode;
if (node is ImplementsClause &&
!element.isImplementableIn2(request.libraryElement)) {
return;
}
_visitInterfaceElement(element);
}
@override
void visitSetterElement(SetterElement element) {
var variable = element.variable3;
if (opType.includeReturnValueSuggestions ||
(opType.includeAnnotationSuggestions &&
variable != null &&
variable.isConst)) {
var parent = element.enclosingElement2;
if (parent is InterfaceElement || parent is ExtensionElement) {
if (!element.isSynthetic) {
builder.suggestSetter(element, inheritanceDistance: 0.0);
}
} else {
builder.suggestTopLevelSetter(element, prefix: prefix);
}
}
}
@override
void visitTopLevelFunctionElement(TopLevelFunctionElement element) {
var returnType = element.returnType;
if (returnType is VoidType) {
if (opType.includeVoidReturnSuggestions) {
builder.suggestTopLevelFunction(element, kind: kind, prefix: prefix);
}
} else {
if (opType.includeReturnValueSuggestions) {
builder.suggestTopLevelFunction(element, kind: kind, prefix: prefix);
}
}
}
@override
void visitTopLevelVariableElement(TopLevelVariableElement element) {
if (opType.includeReturnValueSuggestions && !element.isSynthetic) {
builder.suggestTopLevelVariable(element, prefix: prefix);
}
}
@override
void visitTypeAliasElement(TypeAliasElement element) {
if (opType.includeTypeNameSuggestions) {
builder.suggestTypeAlias(element, prefix: prefix);
}
}
/// Add constructor suggestions for the given class.
///
/// If [onlyConst] is `true`, only `const` constructors will be suggested.
void _addConstructorSuggestions(
ClassElement element, {
bool onlyConst = false,
}) {
if (element is EnumElement) {
return;
}
for (var constructor in element.constructors2) {
if (constructor.isPrivate) {
continue;
}
if (!element.isConstructable && !constructor.isFactory) {
continue;
}
if (onlyConst && !constructor.isConst) {
continue;
}
builder.suggestConstructor(constructor, kind: kind, prefix: prefix);
}
}
void _visitInterfaceElement(InterfaceElement element) {
if (opType.includeTypeNameSuggestions) {
builder.suggestInterface(element, prefix: prefix);
}
if (element is ClassElement) {
if (opType.includeConstructorSuggestions) {
_addConstructorSuggestions(element);
} else if (opType.includeAnnotationSuggestions) {
_addConstructorSuggestions(element, onlyConst: true);
}
}
if (opType.includeReturnValueSuggestions) {
var typeSystem = request.libraryElement.typeSystem;
var contextType = request.contextType;
if (contextType is InterfaceType) {
// TODO(scheglov): This looks not ideal - we should suggest getters.
for (var field in element.fields2) {
if (field.isStatic &&
field.isAccessibleIn2(request.libraryElement) &&
typeSystem.isSubtypeOf(field.type, contextType)) {
if (field.isSynthetic) {
var getter = field.getter2;
if (getter != null) {
builder.suggestGetter(
getter,
inheritanceDistance: 0.0,
withEnclosingName: true,
);
}
} else {
builder.suggestStaticField(field, prefix: prefix);
}
}
}
}
}
}
}