diff --git a/pkg/analyzer/lib/src/dart/element/element.dart b/pkg/analyzer/lib/src/dart/element/element.dart index cc11aa569540..11da360d8fb6 100644 --- a/pkg/analyzer/lib/src/dart/element/element.dart +++ b/pkg/analyzer/lib/src/dart/element/element.dart @@ -4384,6 +4384,7 @@ class FormalParameterElementImpl extends PromotableElementImpl2 with FragmentedAnnotatableElementMixin, FragmentedElementMixin, + FormalParameterElementMixin, _NonTopLevelVariableOrParameter implements FormalParameterElementOrMember { final ParameterElementImpl wrappedElement; @@ -4500,12 +4501,6 @@ class FormalParameterElementImpl extends PromotableElementImpl2 return visitor.visitFormalParameterElement(this); } - @override - void appendToWithoutDelimiters2(StringBuffer buffer) { - // TODO(augmentations): Implement the merge of formal parameters. - wrappedElement.appendToWithoutDelimiters(buffer); - } - @override // TODO(augmentations): Implement the merge of formal parameters. DartObject? computeConstantValue() => wrappedElement.computeConstantValue(); @@ -4521,6 +4516,23 @@ class FormalParameterElementImpl extends PromotableElementImpl2 // .toList(); } +/// A mixin that provides a common implementation for methods defined in +/// [FormalParameterElement]. +mixin FormalParameterElementMixin implements FormalParameterElement { + @override + void appendToWithoutDelimiters2(StringBuffer buffer) { + buffer.write( + type.getDisplayString(), + ); + buffer.write(' '); + buffer.write(displayName); + if (defaultValueCode != null) { + buffer.write(' = '); + buffer.write(defaultValueCode); + } + } +} + abstract class FormalParameterElementOrMember implements FormalParameterElement, diff --git a/pkg/analyzer/lib/src/dart/element/member.dart b/pkg/analyzer/lib/src/dart/element/member.dart index 9f50ea0c225b..65daf638d83b 100644 --- a/pkg/analyzer/lib/src/dart/element/member.dart +++ b/pkg/analyzer/lib/src/dart/element/member.dart @@ -1078,7 +1078,7 @@ class MethodMember extends ExecutableMember /// A parameter element defined in a parameterized type where the values of the /// type parameters are known. class ParameterMember extends VariableMember - with ParameterElementMixin + with ParameterElementMixin, FormalParameterElementMixin implements ParameterElement, FormalParameterElementOrMember { @override final List typeParameters; @@ -1211,11 +1211,6 @@ class ParameterMember extends VariableMember builder.writeFormalParameter(this); } - @override - void appendToWithoutDelimiters2(StringBuffer buffer) { - _element2.appendToWithoutDelimiters2(buffer); - } - @override String displayString2( {bool multiline = false, bool preferTypeAlias = false}) { diff --git a/pkg/analyzer_plugin/analyzer_use_new_elements.txt b/pkg/analyzer_plugin/analyzer_use_new_elements.txt index e9ada8a980b8..b93ede2e6a0e 100644 --- a/pkg/analyzer_plugin/analyzer_use_new_elements.txt +++ b/pkg/analyzer_plugin/analyzer_use_new_elements.txt @@ -1,12 +1,10 @@ lib/src/utilities/completion/completion_target.dart lib/src/utilities/completion/element_suggestion_builder.dart lib/src/utilities/completion/optype.dart -lib/src/utilities/completion/suggestion_builder.dart lib/src/utilities/navigation/navigation_dart.dart lib/src/utilities/visitors/local_declaration_visitor.dart lib/utilities/analyzer_converter.dart lib/utilities/completion/inherited_reference_contributor.dart -lib/utilities/completion/suggestion_builder.dart lib/utilities/completion/type_member_contributor.dart test/src/utilities/change_builder/change_builder_dart_test.dart test/src/utilities/completion/completion_target_test.dart diff --git a/pkg/analyzer_plugin/lib/src/utilities/completion/element_suggestion_builder.dart b/pkg/analyzer_plugin/lib/src/utilities/completion/element_suggestion_builder.dart index 7d683d3dfc1f..0a20dc4a9ab3 100644 --- a/pkg/analyzer_plugin/lib/src/utilities/completion/element_suggestion_builder.dart +++ b/pkg/analyzer_plugin/lib/src/utilities/completion/element_suggestion_builder.dart @@ -4,6 +4,7 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/file_system/file_system.dart'; +import 'package:analyzer/src/utilities/extensions/element.dart'; import 'package:analyzer_plugin/protocol/protocol_common.dart' hide Element, ElementKind; import 'package:analyzer_plugin/protocol/protocol_common.dart' as protocol; @@ -52,7 +53,7 @@ mixin ElementSuggestionBuilder { return; } var builder = SuggestionBuilderImpl(resourceProvider); - var suggestion = builder.forElement(element, + var suggestion = builder.forElement(element.asElement2, completion: completion, kind: kind, relevance: relevance); if (suggestion != null) { if (element.isSynthetic && element is PropertyAccessorElement) { diff --git a/pkg/analyzer_plugin/lib/src/utilities/completion/suggestion_builder.dart b/pkg/analyzer_plugin/lib/src/utilities/completion/suggestion_builder.dart index b0a84a7743b4..a28b2b02d52b 100644 --- a/pkg/analyzer_plugin/lib/src/utilities/completion/suggestion_builder.dart +++ b/pkg/analyzer_plugin/lib/src/utilities/completion/suggestion_builder.dart @@ -2,7 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/element2.dart'; import 'package:analyzer/file_system/file_system.dart'; import 'package:analyzer_plugin/protocol/protocol_common.dart' hide Element, ElementKind; @@ -26,9 +26,9 @@ class SuggestionBuilderImpl implements SuggestionBuilder { /// [requiredParams] and [namedParams]. void addDefaultArgDetails( CompletionSuggestion suggestion, - Element element, - Iterable requiredParams, - Iterable namedParams) { + Element2 element, + Iterable requiredParams, + Iterable namedParams) { // Copied from analysis_server/lib/src/services/completion/dart/suggestion_builder.dart var buffer = StringBuffer(); var ranges = []; @@ -40,17 +40,17 @@ class SuggestionBuilderImpl implements SuggestionBuilder { buffer.write(', '); } offset = buffer.length; - var name = param.name; + var name = param.name3 ?? ''; buffer.write(name); ranges.addAll([offset, name.length]); } for (var param in namedParams) { - if (param.hasRequired || param.isRequiredNamed) { + if (param.metadata2.hasRequired || param.isRequiredNamed) { if (buffer.isNotEmpty) { buffer.write(', '); } - var name = param.name; + var name = param.name3 ?? ''; buffer.write('$name: '); offset = buffer.length; buffer.write(name); @@ -65,7 +65,7 @@ class SuggestionBuilderImpl implements SuggestionBuilder { } @override - CompletionSuggestion? forElement(Element? element, + CompletionSuggestion? forElement(Element2? element, {String? completion, CompletionSuggestionKind? kind, int relevance = DART_RELEVANCE_DEFAULT}) { @@ -73,12 +73,18 @@ class SuggestionBuilderImpl implements SuggestionBuilder { if (element == null) { return null; } - if (element is ExecutableElement && element.isOperator) { + if (element is MethodElement2 && element.isOperator) { // Do not include operators in suggestions return null; } completion ??= element.displayName; - var isDeprecated = element.hasDeprecated; + + Annotatable? annotatable; + if (element case Annotatable annotatable2) { + annotatable = annotatable2; + } + + var isDeprecated = annotatable?.metadata2.hasDeprecated ?? false; var suggestion = CompletionSuggestion( kind ?? CompletionSuggestionKind.INVOCATION, isDeprecated ? DART_RELEVANCE_LOW : relevance, @@ -89,31 +95,30 @@ class SuggestionBuilderImpl implements SuggestionBuilder { false); // Attach docs. - var doc = removeDartDocDelimiters(element.documentationComment); + var doc = removeDartDocDelimiters(annotatable?.documentationComment); suggestion.docComplete = doc; suggestion.docSummary = getDartDocSummary(doc); - suggestion.element = converter.convertElement(element); - var enclosingElement = element.enclosingElement3; - if (enclosingElement is ClassElement) { + suggestion.element = converter.convertElement2(element); + var enclosingElement = element.enclosingElement2; + if (enclosingElement is ClassElement2) { suggestion.declaringType = enclosingElement.displayName; } suggestion.returnType = getReturnTypeString(element); - if (element is ExecutableElement && element is! PropertyAccessorElement) { - suggestion.parameterNames = element.parameters - .map((ParameterElement parameter) => parameter.name) + if (element is ExecutableElement2 && element is! PropertyAccessorElement2) { + suggestion.parameterNames = element.formalParameters + .map((parameter) => parameter.name3 ?? '') .toList(); - suggestion.parameterTypes = - element.parameters.map((ParameterElement parameter) { + suggestion.parameterTypes = element.formalParameters.map((parameter) { return parameter.type.getDisplayString(); }).toList(); - var requiredParameters = element.parameters - .where((ParameterElement param) => param.isRequiredPositional); + var requiredParameters = + element.formalParameters.where((param) => param.isRequiredPositional); suggestion.requiredParameterCount = requiredParameters.length; var namedParameters = - element.parameters.where((ParameterElement param) => param.isNamed); + element.formalParameters.where((param) => param.isNamed); suggestion.hasNamedParameters = namedParameters.isNotEmpty; addDefaultArgDetails( @@ -122,20 +127,20 @@ class SuggestionBuilderImpl implements SuggestionBuilder { return suggestion; } - String? getReturnTypeString(Element element) { + String? getReturnTypeString(Element2 element) { // Copied from analysis_server/lib/src/protocol_server.dart - if (element is ExecutableElement) { + if (element is ExecutableElement2) { if (element.kind == ElementKind.SETTER) { return null; } else { return element.returnType.getDisplayString(); } - } else if (element is VariableElement) { + } else if (element is VariableElement2) { var type = element.type; return type.getDisplayString(); - } else if (element is TypeAliasElement) { - var aliasedElement = element.aliasedElement; - if (aliasedElement is GenericFunctionTypeElement) { + } else if (element is TypeAliasElement2) { + var aliasedElement = element.aliasedElement2; + if (aliasedElement is GenericFunctionTypeElement2) { var returnType = aliasedElement.returnType; return returnType.getDisplayString(); } else { diff --git a/pkg/analyzer_plugin/lib/utilities/analyzer_converter.dart b/pkg/analyzer_plugin/lib/utilities/analyzer_converter.dart index b011886e10b9..cd0bf4fc6903 100644 --- a/pkg/analyzer_plugin/lib/utilities/analyzer_converter.dart +++ b/pkg/analyzer_plugin/lib/utilities/analyzer_converter.dart @@ -3,14 +3,18 @@ // BSD-style license that can be found in the LICENSE file. import 'package:analyzer/dart/element/element.dart' as analyzer; +import 'package:analyzer/dart/element/element2.dart' as engine; import 'package:analyzer/dart/element/type.dart'; import 'package:analyzer/diagnostic/diagnostic.dart' as analyzer; import 'package:analyzer/error/error.dart' as analyzer; import 'package:analyzer/source/error_processor.dart' as analyzer; import 'package:analyzer/source/line_info.dart' as analyzer; import 'package:analyzer/source/source_range.dart' as analyzer; +import 'package:analyzer/source/source_range.dart' as engine; import 'package:analyzer/src/generated/engine.dart' as analyzer; import 'package:analyzer_plugin/protocol/protocol_common.dart' as plugin; +import 'package:analyzer_plugin/protocol/protocol_common.dart'; +import 'package:path/path.dart' as path; /// An object used to convert between objects defined by the 'analyzer' package /// and those defined by the plugin protocol. @@ -144,6 +148,33 @@ class AnalyzerConverter { ); } + Element convertElement2(engine.Element2 element) { + var kind = convertElementToElementKind2(element); + var name = getElementDisplayName2(element); + var elementTypeParameters = _getTypeParametersString2(element); + var aliasedType = _getAliasedTypeString2(element); + var elementParameters = _getParametersString2(element); + var elementReturnType = _getReturnTypeString2(element); + return Element( + kind, + name, + Element.makeFlags( + isPrivate: element.isPrivate, + isDeprecated: (element is engine.Annotatable) && + (element as engine.Annotatable).metadata2.hasDeprecated, + isAbstract: _isAbstract2(element), + isConst: _isConst2(element), + isFinal: _isFinal2(element), + isStatic: _isStatic2(element), + ), + location: newLocation_fromElement2(element), + typeParameters: elementTypeParameters, + aliasedType: aliasedType, + parameters: elementParameters, + returnType: elementReturnType, + ); + } + /// Convert the element [kind] from the 'analyzer' package to an element kind /// defined by the plugin API. /// @@ -154,6 +185,19 @@ class AnalyzerConverter { plugin.ElementKind convertElementKind(analyzer.ElementKind kind) => kind.toPluginElementKind; + /// Return an [ElementKind] corresponding to the given [engine.Element2]. + ElementKind convertElementToElementKind2(engine.Element2 element) { + if (element is engine.EnumElement2) { + return ElementKind.ENUM; + } else if (element is engine.MixinElement2) { + return ElementKind.MIXIN; + } + if (element is engine.FieldElement2 && element.isEnumConstant) { + return ElementKind.ENUM_CONSTANT; + } + return convertElementKind(element.kind); + } + /// Convert the error [severity] from the 'analyzer' package to an analysis /// error severity defined by the plugin API. plugin.AnalysisErrorSeverity convertErrorSeverity( @@ -165,12 +209,36 @@ class AnalyzerConverter { plugin.AnalysisErrorType convertErrorType(analyzer.ErrorType type) => plugin.AnalysisErrorType.values.byName(type.name); + String getElementDisplayName2(engine.Element2 element) { + if (element is engine.LibraryFragment) { + return path.basename((element as engine.LibraryFragment).source.fullName); + } else { + return element.displayName; + } + } + /// Create a location based on an the given [element]. // TODO(srawlins): Deprecate this. plugin.Location? locationFromElement(analyzer.Element? element, {int? offset, int? length}) => element.toLocation(offset: offset, length: length); + /// Create a Location based on an [engine.Element2]. + Location? newLocation_fromElement2(engine.Element2? element) { + if (element == null) { + return null; + } + if (element is engine.FormalParameterElement && + element.enclosingElement2 == null) { + return null; + } + var fragment = element.firstFragment; + var offset = fragment.nameOffset2 ?? 0; + var length = fragment.name2?.length ?? 0; + var range = engine.SourceRange(offset, length); + return _locationForArgs2(fragment, range); + } + /// Convert the element kind of the [element] from the 'analyzer' package to /// an element kind defined by the plugin API. plugin.ElementKind _convertElementToElementKind(analyzer.Element element) { @@ -202,6 +270,14 @@ class AnalyzerConverter { return null; } + String? _getAliasedTypeString2(engine.Element2 element) { + if (element is engine.TypeAliasElement2) { + var aliasedType = element.aliasedType; + return aliasedType.getDisplayString(); + } + return null; + } + /// Return a textual representation of the parameters of the given [element], /// or `null` if the element does not have any parameters. String? _getParametersString(analyzer.Element element) { @@ -248,6 +324,55 @@ class AnalyzerConverter { return buffer.toString(); } + String? _getParametersString2(engine.Element2 element) { + // TODO(scheglov): expose the corresponding feature from ExecutableElement + List parameters; + if (element is engine.ExecutableElement2) { + // valid getters don't have parameters + if (element.kind == engine.ElementKind.GETTER && + element.formalParameters.isEmpty) { + return null; + } + parameters = element.formalParameters.toList(); + } else if (element is engine.TypeAliasElement2) { + var aliasedType = element.aliasedType; + if (aliasedType is FunctionType) { + parameters = aliasedType.formalParameters.toList(); + } else { + return null; + } + } else { + return null; + } + + parameters.sort(_preferRequiredParams2); + + var sb = StringBuffer(); + var closeOptionalString = ''; + for (var parameter in parameters) { + if (sb.isNotEmpty) { + sb.write(', '); + } + if (closeOptionalString.isEmpty) { + if (parameter.isNamed) { + sb.write('{'); + closeOptionalString = '}'; + } else if (parameter.isOptionalPositional) { + sb.write('['); + closeOptionalString = ']'; + } + } + if (parameter.isRequiredNamed) { + sb.write('required '); + } else if (parameter.metadata2.hasDeprecated) { + sb.write('@required '); + } + parameter.appendToWithoutDelimiters2(sb); + } + sb.write(closeOptionalString); + return '($sb)'; + } + /// Return a textual representation of the return type of the given [element], /// or `null` if the element does not have a return type. String? _getReturnTypeString(analyzer.Element element) { @@ -268,6 +393,26 @@ class AnalyzerConverter { return null; } + String? _getReturnTypeString2(engine.Element2 element) { + if (element is engine.ExecutableElement2) { + if (element.kind == engine.ElementKind.SETTER) { + return null; + } else { + return element.returnType.getDisplayString(); + } + } else if (element is engine.VariableElement2) { + var type = element.type; + return type.getDisplayString(); + } else if (element is engine.TypeAliasElement2) { + var aliasedType = element.aliasedType; + if (aliasedType is FunctionType) { + var returnType = aliasedType.returnType; + return returnType.getDisplayString(); + } + } + return null; + } + /// Return a textual representation of the type parameters of the given /// [element], or `null` if the element does not have type parameters. String? _getTypeParametersString(analyzer.Element element) { @@ -281,6 +426,19 @@ class AnalyzerConverter { return null; } + String? _getTypeParametersString2(engine.Element2 element) { + List? typeParameters; + if (element is engine.InterfaceElement2) { + typeParameters = element.typeParameters2; + } else if (element is engine.TypeAliasElement2) { + typeParameters = element.typeParameters2; + } + if (typeParameters == null || typeParameters.isEmpty) { + return null; + } + return '<${typeParameters.join(', ')}>'; + } + bool _isAbstract(analyzer.Element element) { // TODO(scheglov): add isAbstract to Element API if (element is analyzer.ClassElement) { @@ -293,6 +451,19 @@ class AnalyzerConverter { return false; } + bool _isAbstract2(engine.Element2 element) { + if (element is engine.ClassElement2) { + return element.isAbstract; + } + if (element is engine.MethodElement2) { + return element.isAbstract; + } + if (element is engine.MixinElement2) { + return true; + } + return false; + } + bool _isConst(analyzer.Element element) { // TODO(scheglov): add isConst to Element API if (element is analyzer.ConstructorElement) { @@ -303,6 +474,16 @@ class AnalyzerConverter { return false; } + bool _isConst2(engine.Element2 element) { + if (element is engine.ConstructorElement2) { + return element.isConst; + } + if (element is engine.VariableElement2) { + return element.isConst; + } + return false; + } + bool _isFinal(analyzer.Element element) { // TODO(scheglov): add isFinal to Element API if (element is analyzer.VariableElement) { @@ -311,6 +492,13 @@ class AnalyzerConverter { return false; } + bool _isFinal2(engine.Element2 element) { + if (element is engine.VariableElement2) { + return element.isFinal; + } + return false; + } + bool _isStatic(analyzer.Element element) { // TODO(scheglov): add isStatic to Element API if (element is analyzer.ExecutableElement) { @@ -320,6 +508,64 @@ class AnalyzerConverter { } return false; } + + bool _isStatic2(engine.Element2 element) { + if (element is engine.ExecutableElement2) { + return element.isStatic; + } + if (element is engine.PropertyInducingElement2) { + return element.isStatic; + } + return false; + } + + /// Creates a new [Location]. + Location? _locationForArgs2( + engine.Fragment fragment, + engine.SourceRange range, + ) { + var libraryFragment = fragment.libraryFragment; + if (libraryFragment == null) { + return null; + } + var lineInfo = libraryFragment.lineInfo; + + var startLocation = lineInfo.getLocation(range.offset); + var endLocation = lineInfo.getLocation(range.end); + + var startLine = startLocation.lineNumber; + var startColumn = startLocation.columnNumber; + var endLine = endLocation.lineNumber; + var endColumn = endLocation.columnNumber; + + return Location( + fragment.libraryFragment!.source.fullName, + range.offset, + range.length, + startLine, + startColumn, + endLine: endLine, + endColumn: endColumn, + ); + } + + /// Sort required named parameters before optional ones. + int _preferRequiredParams2( + engine.FormalParameterElement e1, + engine.FormalParameterElement e2, + ) { + var rank1 = (e1.isRequiredNamed || e1.metadata2.hasRequired) + ? 0 + : !e1.isNamed + ? -1 + : 1; + var rank2 = (e2.isRequiredNamed || e2.metadata2.hasRequired) + ? 0 + : !e2.isNamed + ? -1 + : 1; + return rank1 - rank2; + } } // TODO(srawlins): Move this to a better location. diff --git a/pkg/analyzer_plugin/lib/utilities/completion/suggestion_builder.dart b/pkg/analyzer_plugin/lib/utilities/completion/suggestion_builder.dart index 2c8531c09ee7..ad4224b20dea 100644 --- a/pkg/analyzer_plugin/lib/utilities/completion/suggestion_builder.dart +++ b/pkg/analyzer_plugin/lib/utilities/completion/suggestion_builder.dart @@ -2,7 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/element2.dart'; import 'package:analyzer_plugin/protocol/protocol_common.dart' hide Element; import 'package:analyzer_plugin/utilities/completion/relevance.dart'; @@ -10,7 +10,7 @@ import 'package:analyzer_plugin/utilities/completion/relevance.dart'; abstract class SuggestionBuilder { /// Return a suggestion based on the given [element], or `null` if a /// suggestion is not appropriate for the given element. - CompletionSuggestion? forElement(Element element, + CompletionSuggestion? forElement(Element2 element, {String? completion, CompletionSuggestionKind kind = CompletionSuggestionKind.INVOCATION, int relevance = DART_RELEVANCE_DEFAULT}); diff --git a/pkg/analyzer_plugin/lib/utilities/completion/type_member_contributor.dart b/pkg/analyzer_plugin/lib/utilities/completion/type_member_contributor.dart index 156236d6d06f..c4c9856cf6ee 100644 --- a/pkg/analyzer_plugin/lib/utilities/completion/type_member_contributor.dart +++ b/pkg/analyzer_plugin/lib/utilities/completion/type_member_contributor.dart @@ -9,6 +9,7 @@ import 'package:analyzer/dart/ast/token.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:analyzer/file_system/file_system.dart'; +import 'package:analyzer/src/utilities/extensions/element.dart'; import 'package:analyzer_plugin/protocol/protocol_common.dart' hide Element; import 'package:analyzer_plugin/src/utilities/completion/completion_target.dart'; import 'package:analyzer_plugin/src/utilities/completion/suggestion_builder.dart'; @@ -397,7 +398,8 @@ class _SuggestionBuilder { assert(false); return; } - var suggestion = builder.forElement(element, relevance: relevance); + var suggestion = + builder.forElement(element.asElement2!, relevance: relevance); if (suggestion != null) { _suggestionMap[suggestion.completion] = suggestion; }