From 38cec67d032ef0fb6dff662fa4e5dcd3863db062 Mon Sep 17 00:00:00 2001 From: danrubel Date: Mon, 23 Nov 2015 12:50:12 -0500 Subject: [PATCH] step toward new completion API - separate new CompletionContributor and DartCompletionContributor APIs - revise new APIs and implementations to be Futures based - revise InheritedContributor to use new Futures based contributor API - remove unused classes R=brianwilkerson@google.com, scheglov@google.com Review URL: https://codereview.chromium.org/1467023002 . --- .../completion/completion_core.dart | 17 +- .../completion/completion_dart.dart | 72 ++---- .../services/completion/completion_dart.dart | 89 +++++-- .../dart/inherited_contributor.dart | 175 ++++++++++++++ .../completion/dart_completion_manager.dart | 84 ------- .../completion/inherited_contributor.dart | 160 ------------- .../dart_completion_contributor_test.dart | 220 ++++++++++++++++++ .../dart/inherited_contributor_test.dart | 122 ++++++++++ .../services/completion/dart/test_all.dart | 18 ++ .../completion/inherited_computer_test.dart | 75 ------ .../test/services/completion/test_all.dart | 4 +- 11 files changed, 640 insertions(+), 396 deletions(-) create mode 100644 pkg/analysis_server/lib/src/services/completion/dart/inherited_contributor.dart delete mode 100644 pkg/analysis_server/lib/src/services/completion/inherited_contributor.dart create mode 100644 pkg/analysis_server/test/services/completion/dart/dart_completion_contributor_test.dart create mode 100644 pkg/analysis_server/test/services/completion/dart/inherited_contributor_test.dart create mode 100644 pkg/analysis_server/test/services/completion/dart/test_all.dart delete mode 100644 pkg/analysis_server/test/services/completion/inherited_computer_test.dart diff --git a/pkg/analysis_server/lib/src/provisional/completion/completion_core.dart b/pkg/analysis_server/lib/src/provisional/completion/completion_core.dart index af69cd0152f..dff6ba6befa 100644 --- a/pkg/analysis_server/lib/src/provisional/completion/completion_core.dart +++ b/pkg/analysis_server/lib/src/provisional/completion/completion_core.dart @@ -4,12 +4,20 @@ library analysis_server.src.provisional.completion.completion_core; +import 'dart:async'; + import 'package:analysis_server/plugin/protocol/protocol.dart'; import 'package:analyzer/file_system/file_system.dart'; import 'package:analyzer/src/generated/engine.dart' show AnalysisContext; import 'package:analyzer/src/generated/source.dart'; import 'package:analyzer/task/model.dart'; +/** + * An empty list returned by [CompletionContributor]s + * when they have no suggestions to contribute. + */ +const EMPTY_LIST = const []; + /** * A method or function called when the requested analysis has been performed. */ @@ -41,14 +49,15 @@ class AnalysisRequest { /** * An object used to produce completions at a specific location within a file. * - * Clients may extend this class when implementing plugins. + * Clients may implement this class when implementing plugins. */ abstract class CompletionContributor { /** - * Compute a list of completion suggestions based on the given completion - * [request]. Return the suggestions that were computed. + * Return a [Future] that completes with a list of suggestions + * for the given completion [request]. */ - List computeSuggestions(CompletionRequest request); + Future> computeSuggestions( + CompletionRequest request); } /** diff --git a/pkg/analysis_server/lib/src/provisional/completion/completion_dart.dart b/pkg/analysis_server/lib/src/provisional/completion/completion_dart.dart index e52c4332423..04333641e88 100644 --- a/pkg/analysis_server/lib/src/provisional/completion/completion_dart.dart +++ b/pkg/analysis_server/lib/src/provisional/completion/completion_dart.dart @@ -4,55 +4,25 @@ library analysis_server.src.provisional.completion.completion_dart; +import 'dart:async'; + import 'package:analysis_server/plugin/protocol/protocol.dart'; import 'package:analysis_server/src/provisional/completion/completion_core.dart'; import 'package:analysis_server/src/provisional/completion/dart/completion_target.dart'; import 'package:analyzer/src/generated/ast.dart'; -import 'package:analyzer/src/generated/engine.dart'; -import 'package:analyzer/src/generated/source.dart'; /** - * An object used to produce completions for a specific error within a Dart - * file. Completion contributors are long-lived objects and must not retain any - * state between invocations of [computeSuggestions]. + * An object used to produce completions + * at a specific location within a Dart file. * - * Clients may extend this class when implementing plugins. + * Clients may implement this class when implementing plugins. */ -abstract class DartCompletionContributor implements CompletionContributor { - @override - List computeSuggestions(CompletionRequest request) { - if (request is DartCompletionRequest) { - return internalComputeSuggestions(request); - } - AnalysisContext context = request.context; - Source source = request.source; - List libraries = context.getLibrariesContaining(source); - if (libraries.length < 1) { - return null; - } -// CompilationUnit unit = -// context.getResolvedCompilationUnit2(source, libraries[0]); -// bool isResolved = true; -// if (unit == null) { -// // TODO(brianwilkerson) Implement a method for getting a parsed -// // compilation unit without parsing the unit if it hasn't been parsed. -// unit = context.getParsedCompilationUnit(source); -// if (unit == null) { -// return null; -// } -// isResolved = false; -// } -// DartCompletionRequest dartRequest = -// new DartCompletionRequestImpl(request, unit, isResolved); -// return internalComputeSuggestions(dartRequest); - return null; - } - +abstract class DartCompletionContributor { /** - * Compute a list of completion suggestions based on the given completion - * [request]. Return the suggestions that were computed. + * Return a [Future] that completes with a list of suggestions + * for the given completion [request]. */ - List internalComputeSuggestions( + Future> computeSuggestions( DartCompletionRequest request); } @@ -62,11 +32,6 @@ abstract class DartCompletionContributor implements CompletionContributor { * Clients may not extend, implement or mix-in this class. */ abstract class DartCompletionRequest extends CompletionRequest { - /** - * Return `true` if the compilation [unit] is resolved. - */ - bool get isResolved; - /** * Return the completion target. This determines what part of the parse tree * will receive the newly inserted text. @@ -74,17 +39,12 @@ abstract class DartCompletionRequest extends CompletionRequest { CompletionTarget get target; /** - * Cached information from a prior code completion operation. + * Return a [Future] that completes with a compilation unit in which + * all declarations in all scopes containing [target] have been resolved. + * The [Future] may return `null` if the unit cannot be resolved + * (e.g. unlinked part file). + * Any information obtained from [target] prior to calling this method + * should be discarded as it may have changed. */ - //DartCompletionCache get cache; - - /** - * Return the compilation unit in which the completion was requested. - */ - CompilationUnit get unit; - - /** - * Information about the types of suggestions that should be included. - */ - //OpType get _optype; + Future resolveDeclarationsInScope(); } diff --git a/pkg/analysis_server/lib/src/services/completion/completion_dart.dart b/pkg/analysis_server/lib/src/services/completion/completion_dart.dart index 1cfff042d60..1ccc28b5ede 100644 --- a/pkg/analysis_server/lib/src/services/completion/completion_dart.dart +++ b/pkg/analysis_server/lib/src/services/completion/completion_dart.dart @@ -4,11 +4,21 @@ library analysis_server.src.services.completion.completion_dart; -import 'package:analysis_server/src/provisional/completion/completion_core.dart'; +import 'dart:async'; + +import 'package:analysis_server/src/provisional/completion/completion_core.dart' + show CompletionRequest; import 'package:analysis_server/src/provisional/completion/completion_dart.dart'; import 'package:analysis_server/src/provisional/completion/dart/completion_target.dart'; import 'package:analysis_server/src/services/completion/completion_core.dart'; +import 'package:analyzer/file_system/file_system.dart'; +import 'package:analyzer/src/context/context.dart' + show AnalysisFutureHelper, AnalysisContextImpl; import 'package:analyzer/src/generated/ast.dart'; +import 'package:analyzer/src/generated/engine.dart' hide AnalysisContextImpl; +import 'package:analyzer/src/generated/source.dart'; +import 'package:analyzer/src/task/dart.dart'; +import 'package:analyzer/task/dart.dart'; /** * The information about a requested list of completions within a Dart file. @@ -16,26 +26,75 @@ import 'package:analyzer/src/generated/ast.dart'; class DartCompletionRequestImpl extends CompletionRequestImpl implements DartCompletionRequest { /** - * The compilation unit in which the completion was requested. + * The cached completion target or `null` if not computed yet. */ - final CompilationUnit unit; + CompletionTarget _target; /** - * A flag indicating whether the compilation [unit] is resolved. + * `true` if [resolveDeclarationsInScope] has partially resolved the unit + * referenced by [target], else `false`. */ - final bool isResolved; + bool _haveResolveDeclarationsInScope = false; /** - * The completion target. This determines what part of the parse tree - * will receive the newly inserted text. + * Initialize a newly created completion request based on the given request. */ - final CompletionTarget target; + factory DartCompletionRequestImpl.forRequest(CompletionRequest request) { + return new DartCompletionRequestImpl._(request.context, + request.resourceProvider, request.source, request.offset); + } - /** - * Initialize a newly created completion request based on the given arguments. - */ - DartCompletionRequestImpl( - CompletionRequest request, this.unit, this.isResolved, this.target) - : super(request.context, request.resourceProvider, request.source, - request.offset); + DartCompletionRequestImpl._(AnalysisContext context, + ResourceProvider resourceProvider, Source source, int offset) + : super(context, resourceProvider, source, offset); + + @override + Future resolveDeclarationsInScope() async { + CompilationUnit unit = target.unit; + if (_haveResolveDeclarationsInScope) { + return unit; + } + + // Determine the library source + Source librarySource; + if (unit.directives.any((d) => d is PartOfDirective)) { + List libraries = context.getLibrariesContaining(source); + if (libraries.isEmpty) { + return null; + } + librarySource = libraries[0]; + } else { + librarySource = source; + } + + // Resolve declarations in the target unit + CompilationUnit resolvedUnit = + await new AnalysisFutureHelper( + context, + new LibrarySpecificUnit(librarySource, source), + RESOLVED_UNIT3).computeAsync(); + + // TODO(danrubel) determine if the underlying source has been modified + // in a way that invalidates the completion request + // and return null + + // Gracefully degrade if unit cannot be resolved + if (resolvedUnit == null) { + return null; + } + + // Recompute the target for the newly resolved unit + _target = new CompletionTarget.forOffset(resolvedUnit, offset); + _haveResolveDeclarationsInScope = true; + return resolvedUnit; + } + + @override + CompletionTarget get target { + if (_target == null) { + CompilationUnit unit = context.computeResult(source, PARSED_UNIT); + _target = new CompletionTarget.forOffset(unit, offset); + } + return _target; + } } diff --git a/pkg/analysis_server/lib/src/services/completion/dart/inherited_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/inherited_contributor.dart new file mode 100644 index 00000000000..3b56e408814 --- /dev/null +++ b/pkg/analysis_server/lib/src/services/completion/dart/inherited_contributor.dart @@ -0,0 +1,175 @@ +// Copyright (c) 2015, 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. + +library services.completion.computer.dart.invocation; + +import 'dart:async'; + +import 'package:analysis_server/src/protocol_server.dart' + show CompletionSuggestion, CompletionSuggestionKind, SourceChange; +import 'package:analysis_server/src/protocol_server.dart' as protocol + hide CompletionSuggestion, CompletionSuggestionKind; +import 'package:analysis_server/src/provisional/completion/completion_core.dart'; +import 'package:analysis_server/src/provisional/completion/completion_dart.dart'; +import 'package:analysis_server/src/provisional/completion/dart/completion_target.dart'; +import 'package:analysis_server/src/services/completion/dart_completion_manager.dart' + show DART_RELEVANCE_HIGH; +import 'package:analyzer/src/generated/ast.dart'; +import 'package:analyzer/src/generated/element.dart'; +import 'package:analyzer/src/generated/resolver.dart'; +import 'package:analyzer/src/generated/source.dart'; +// import 'package:analysis_server/plugin/edit/utilities/change_builder_dart.dart'; +// import 'package:analyzer/src/generated/engine.dart'; + +/** + * A completion contributor used to suggest replacing partial identifiers inside + * a class declaration with templates for inherited members. + */ +class InheritedContributor implements DartCompletionContributor { + @override + Future> computeSuggestions( + DartCompletionRequest request) async { + // Determine if the target looks like a partial identifier + // inside a class declaration + SimpleIdentifier targetId = _getTargetId(request.target); + if (targetId == null) { + return EMPTY_LIST; + } + + // Partially resolve the compilation unit + CompilationUnit unit = await request.resolveDeclarationsInScope(); + // Gracefully degrade if the compilation unit could not be resolved + // e.g. detached part file or source change + if (unit == null) { + return EMPTY_LIST; + } + + // Recompute the target since resolution may have changed it + targetId = _getTargetId(request.target); + if (targetId == null) { + return EMPTY_LIST; + } + ClassDeclaration classDecl = + targetId.getAncestor((p) => p is ClassDeclaration); + if (classDecl == null) { + return EMPTY_LIST; + } + + // Generate a collection of inherited members + ClassElement classElem = classDecl.element; + InheritanceManager manager = new InheritanceManager(classElem.library); + MemberMap map = manager.getMapOfMembersInheritedFromInterfaces(classElem); + List memberNames = _computeMemberNames(map, classElem); + + // Build suggestions + List suggestions = []; + for (String memberName in memberNames) { + ExecutableElement element = map.get(memberName); + // Gracefully degrade if the overridden element has not been resolved. + if (element.returnType != null) { + CompletionSuggestion suggestion = + _buildSuggestion(request, targetId, unit, element); + if (suggestion != null) { + suggestions.add(suggestion); + } + } + } + return suggestions; + } + + /** + * If the target looks like a partial identifier inside a class declaration + * then return that identifier, otherwise return `null`. + */ + SimpleIdentifier _getTargetId(CompletionTarget target) { + AstNode node = target.containingNode; + if (node is ClassDeclaration) { + Object entity = target.entity; + if (entity is FieldDeclaration) { + NodeList variables = entity.fields.variables; + if (variables.length == 1) { + SimpleIdentifier targetId = variables[0].name; + if (targetId.name.isEmpty) { + return targetId; + } + } + } + } + return null; + } + + /** + * Return a template for an override of the given [element] in the given + * [source]. If selected, the template will replace [targetId]. + */ + String _buildRepacementText(Source source, SimpleIdentifier targetId, + CompilationUnit unit, ExecutableElement element) { + // AnalysisContext context = element.context; + // Inject partially resolved unit for use by change builder + // DartChangeBuilder builder = new DartChangeBuilder(context, unit); + // builder.addFileEdit(source, context.getModificationStamp(source), + // (DartFileEditBuilder builder) { + // builder.addReplacement(targetId.offset, targetId.length, + // (DartEditBuilder builder) { + // builder.writeOverrideOfInheritedMember(element); + // }); + // }); + // return builder.sourceChange.edits[0].edits[0].replacement.trim(); + return ''; + } + + /** + * Build a suggestion to replace [targetId] in the given [unit] + * with an override of the given [element]. + */ + CompletionSuggestion _buildSuggestion( + DartCompletionRequest request, + SimpleIdentifier targetId, + CompilationUnit unit, + ExecutableElement element) { + String completion = + _buildRepacementText(request.source, targetId, unit, element); + if (completion == null || completion.length == 0) { + return null; + } + CompletionSuggestion suggestion = new CompletionSuggestion( + CompletionSuggestionKind.IDENTIFIER, + DART_RELEVANCE_HIGH, + completion, + targetId.offset, + 0, + element.isDeprecated, + false); + suggestion.element = protocol.convertElement(element); + return suggestion; + } + + /** + * Return a list containing the names of all of the inherited but not + * implemented members of the class represented by the given [element]. + * The [map] is used to find all of the members that are inherited. + */ + List _computeMemberNames(MemberMap map, ClassElement element) { + List memberNames = []; + int count = map.size; + for (int i = 0; i < count; i++) { + String memberName = map.getKey(i); + if (!_hasMember(element, memberName)) { + memberNames.add(memberName); + } + } + return memberNames; + } + + /** + * Return `true` if the given [classElement] directly declares a member with + * the given [memberName]. + */ + bool _hasMember(ClassElement classElement, String memberName) { + return classElement.getField(memberName) != null || + classElement.getGetter(memberName) != null || + classElement.getMethod(memberName) != null || + classElement.getSetter(memberName) != null; + } +} diff --git a/pkg/analysis_server/lib/src/services/completion/dart_completion_manager.dart b/pkg/analysis_server/lib/src/services/completion/dart_completion_manager.dart index 2edb62a95be..05a8abd7022 100644 --- a/pkg/analysis_server/lib/src/services/completion/dart_completion_manager.dart +++ b/pkg/analysis_server/lib/src/services/completion/dart_completion_manager.dart @@ -10,8 +10,6 @@ import 'package:analysis_server/plugin/protocol/protocol.dart'; import 'package:analysis_server/src/analysis_server.dart'; import 'package:analysis_server/src/provisional/completion/completion_core.dart' show AnalysisRequest, CompletionRequest; -import 'package:analysis_server/src/provisional/completion/completion_dart.dart' - as newApi; import 'package:analysis_server/src/provisional/completion/dart/completion_target.dart'; import 'package:analysis_server/src/services/completion/arglist_contributor.dart'; import 'package:analysis_server/src/services/completion/combinator_contributor.dart'; @@ -26,15 +24,12 @@ import 'package:analysis_server/src/services/completion/optype.dart'; import 'package:analysis_server/src/services/completion/prefixed_element_contributor.dart'; import 'package:analysis_server/src/services/completion/uri_contributor.dart'; import 'package:analysis_server/src/services/search/search_engine.dart'; -import 'package:analyzer/file_system/file_system.dart'; -import 'package:analyzer/src/cancelable_future.dart'; import 'package:analyzer/src/context/context.dart' show AnalysisFutureHelper, AnalysisContextImpl; import 'package:analyzer/src/generated/ast.dart'; import 'package:analyzer/src/generated/engine.dart' hide AnalysisContextImpl; import 'package:analyzer/src/generated/scanner.dart'; import 'package:analyzer/src/generated/source.dart'; -import 'package:analyzer/task/model.dart'; const int DART_RELEVANCE_COMMON_USAGE = 1200; const int DART_RELEVANCE_DEFAULT = 1000; @@ -459,82 +454,3 @@ class DartCompletionRequest extends CompletionRequestImpl { } } } - -/** - * A wrapper around a new dart completion contributor that makes it usable where - * an old dart completion contributor is expected. - */ -class NewCompletionWrapper implements DartCompletionContributor { - /** - * The new-style contributor that is being wrapped. - */ - final newApi.DartCompletionContributor contributor; - - /** - * Initialize a newly created wrapper for the given [contributor]. - */ - NewCompletionWrapper(this.contributor); - - @override - bool computeFast(DartCompletionRequest request) { - List suggestions = - contributor.computeSuggestions(new OldRequestWrapper(request)); - if (suggestions == null) { - return false; - } - for (CompletionSuggestion suggestion in suggestions) { - request.addSuggestion(suggestion); - } - return true; - } - - @override - Future computeFull(DartCompletionRequest request) async { - List suggestions = - contributor.computeSuggestions(new OldRequestWrapper(request)); - if (suggestions != null) { - for (CompletionSuggestion suggestion in suggestions) { - request.addSuggestion(suggestion); - } - return true; - } - return false; - } - - @override - String toString() => 'wrapped $contributor'; -} - -/** - * A wrapper around an old dart completion request that makes it usable where a - * new dart completion request is expected. - */ -class OldRequestWrapper implements newApi.DartCompletionRequest { - final DartCompletionRequest request; - - OldRequestWrapper(this.request); - - @override - AnalysisContext get context => request.context; - - @override - bool get isResolved => request.unit.element != null; - - @override - int get offset => request.offset; - - @override - ResourceProvider get resourceProvider => request.resourceProvider; - - @override - Source get source => request.source; - - @override - CompletionTarget get target => request.target; - - @override - CompilationUnit get unit => request.unit; - - @override - String toString() => 'wrapped $request'; -} diff --git a/pkg/analysis_server/lib/src/services/completion/inherited_contributor.dart b/pkg/analysis_server/lib/src/services/completion/inherited_contributor.dart deleted file mode 100644 index 451da5363ba..00000000000 --- a/pkg/analysis_server/lib/src/services/completion/inherited_contributor.dart +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (c) 2015, 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. - -library services.completion.computer.dart.invocation; - -import 'package:analysis_server/plugin/edit/utilities/change_builder_dart.dart'; -import 'package:analysis_server/src/protocol_server.dart' - show CompletionSuggestion, CompletionSuggestionKind, SourceChange; -import 'package:analysis_server/src/protocol_server.dart' as protocol - hide CompletionSuggestion, CompletionSuggestionKind; -import 'package:analysis_server/src/provisional/completion/completion_dart.dart'; -import 'package:analysis_server/src/services/completion/dart_completion_manager.dart' - show DART_RELEVANCE_HIGH; -import 'package:analyzer/src/generated/ast.dart'; -import 'package:analyzer/src/generated/element.dart'; -import 'package:analyzer/src/generated/engine.dart'; -import 'package:analyzer/src/generated/resolver.dart'; -import 'package:analyzer/src/generated/source.dart'; - -/** - * A completion contributor used to suggest replacing partial identifiers inside - * a class declaration with templates for inherited members. - */ -class InheritedContributor extends DartCompletionContributor { - @override - List internalComputeSuggestions( - DartCompletionRequest request) { - if (!request.isResolved) { - return null; - } - AstNode node = new NodeLocator(request.offset).searchWithin(request.unit); - if (node == null || !_isMemberLevelIdentifier(node)) { - return null; - } - ClassDeclaration classDeclaration = - node.getAncestor((AstNode node) => node is ClassDeclaration); - if (classDeclaration != null) { - ClassElement element = classDeclaration.element; - if (element == null) { - return null; - } - return _suggestInheritedMembers(request, node, element); - } - return null; - } - - /** - * Return a template for an override of the given [element] in the given - * [source]. If selected, the template will replace the given [identifier]. - */ - String _buildRepacementText( - Source source, SimpleIdentifier identifier, Element element) { - AnalysisContext context = element.context; - DartChangeBuilder builder = new DartChangeBuilder(context); - builder.addFileEdit(source, context.getModificationStamp(source), - (DartFileEditBuilder builder) { - builder.addReplacement(identifier.offset, identifier.length, - (DartEditBuilder builder) { - builder.writeOverrideOfInheritedMember(element); - }); - }); - return builder.sourceChange.edits[0].edits[0].replacement.trim(); - } - - /** - * Build a suggestion to replace the partial [identifier] in the given - * [source] with an override of the given [element]. - */ - CompletionSuggestion _buildSuggestion( - Source source, SimpleIdentifier identifier, Element element) { - String completion = _buildRepacementText(source, identifier, element); - CompletionSuggestion suggestion = new CompletionSuggestion( - CompletionSuggestionKind.IDENTIFIER, - DART_RELEVANCE_HIGH, - completion, - identifier.offset, - 0, - element.isDeprecated, - false); - suggestion.element = protocol.convertElement(element); - return suggestion; - } - - /** - * Return a list containing the names of all of the inherited by not - * implemented members of the class represented by the given [element] that - * start with the given [prefix]. The [map] is used to find all of the members - * that are inherited. - */ - List _computeMemberNames( - MemberMap map, ClassElement element, String prefix) { - List memberNames = []; - int count = map.size; - for (int i = 0; i < count; i++) { - String memberName = map.getKey(i); - if (memberName.startsWith(prefix) && !_hasMember(element, memberName)) { - memberNames.add(memberName); - } - } - return memberNames; - } - - /** - * Return `true` if the given [classElement] directly declares a member with - * the given [memberName]. - */ - bool _hasMember(ClassElement classElement, String memberName) { - return classElement.getField(memberName) != null || - classElement.getGetter(memberName) != null || - classElement.getMethod(memberName) != null || - classElement.getSetter(memberName) != null; - } - - /** - * Return `true` if the given [node] looks like a partial identifier inside a - * class declaration. - */ - bool _isMemberLevelIdentifier(AstNode node) { - if (node is SimpleIdentifier) { - AstNode parent1 = node.parent; - if (parent1 is TypeName) { - AstNode parent2 = parent1.parent; - if (parent2 is VariableDeclarationList) { - AstNode parent3 = parent2.parent; - if (parent3 is FieldDeclaration) { - NodeList variables = parent2.variables; - return variables.length == 1 && variables[0].name.name.isEmpty; - } - } - } - } - return false; - } - - /** - * Add any suggestions that are appropriate to the given [request], using the - * given [element] to find inherited members whose name has the given - * [identifier] as a prefix. - */ - List _suggestInheritedMembers( - DartCompletionRequest request, - SimpleIdentifier identifier, - ClassElement element) { - String name = identifier.name; - InheritanceManager manager = new InheritanceManager(element.library); - MemberMap map = manager.getMapOfMembersInheritedFromInterfaces(element); - List memberNames = _computeMemberNames(map, element, name); - memberNames.sort(); - List suggestions = []; - for (String memberName in memberNames) { - CompletionSuggestion suggestion = - _buildSuggestion(request.source, identifier, map.get(memberName)); - if (suggestion != null) { - suggestions.add(suggestion); - } - } - return suggestions; - } -} diff --git a/pkg/analysis_server/test/services/completion/dart/dart_completion_contributor_test.dart b/pkg/analysis_server/test/services/completion/dart/dart_completion_contributor_test.dart new file mode 100644 index 00000000000..0c62fe4d68c --- /dev/null +++ b/pkg/analysis_server/test/services/completion/dart/dart_completion_contributor_test.dart @@ -0,0 +1,220 @@ +// 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. + +library test.services.completion.contributor.dart; + +import 'dart:async'; + +import 'package:analysis_server/plugin/protocol/protocol.dart' as protocol + show Element, ElementKind; +import 'package:analysis_server/plugin/protocol/protocol.dart' + hide Element, ElementKind; +import 'package:analysis_server/plugin/protocol/protocol.dart'; +import 'package:analysis_server/src/provisional/completion/completion_core.dart' + show AnalysisRequest, CompletionContributor, CompletionRequest; +import 'package:analysis_server/src/provisional/completion/completion_dart.dart'; +import 'package:analysis_server/src/services/completion/completion_dart.dart' + show DartCompletionRequestImpl; +import 'package:analysis_server/src/services/completion/dart_completion_manager.dart' + show DART_RELEVANCE_DEFAULT, DART_RELEVANCE_LOW; +import 'package:analysis_server/src/services/index/index.dart'; +import 'package:analysis_server/src/services/index/local_memory_index.dart'; +import 'package:analysis_server/src/services/search/search_engine_internal.dart'; +import 'package:analyzer/src/generated/source.dart'; +import 'package:unittest/unittest.dart'; + +import '../../../abstract_context.dart'; +import 'package:analysis_server/src/services/completion/completion_core.dart'; + +int suggestionComparator(CompletionSuggestion s1, CompletionSuggestion s2) { + String c1 = s1.completion.toLowerCase(); + String c2 = s2.completion.toLowerCase(); + return c1.compareTo(c2); +} + +abstract class DartCompletionContributorTest extends AbstractContextTest { + Index index; + SearchEngineImpl searchEngine; + String testFile = '/completionTest.dart'; + Source testSource; + int completionOffset; + DartCompletionContributor contributor; + CompletionRequest request; + List suggestions; + + void addTestSource(String content) { + expect(completionOffset, isNull, reason: 'Call addTestUnit exactly once'); + completionOffset = content.indexOf('^'); + expect(completionOffset, isNot(equals(-1)), reason: 'missing ^'); + int nextOffset = content.indexOf('^', completionOffset + 1); + expect(nextOffset, equals(-1), reason: 'too many ^'); + content = content.substring(0, completionOffset) + + content.substring(completionOffset + 1); + testSource = addSource(testFile, content); + } + + void assertHasParameterInfo(CompletionSuggestion suggestion) { + expect(suggestion.parameterNames, isNotNull); + expect(suggestion.parameterTypes, isNotNull); + expect(suggestion.parameterNames.length, suggestion.parameterTypes.length); + expect(suggestion.requiredParameterCount, + lessThanOrEqualTo(suggestion.parameterNames.length)); + expect(suggestion.hasNamedParameters, isNotNull); + } + + void assertNotSuggested(String completion) { + CompletionSuggestion suggestion = suggestions.firstWhere( + (CompletionSuggestion cs) => cs.completion == completion, + orElse: () => null); + if (suggestion != null) { + failedCompletion('did not expect completion: $completion\n $suggestion'); + } + } + + CompletionSuggestion assertSuggest(String completion, + {CompletionSuggestionKind csKind: CompletionSuggestionKind.INVOCATION, + int relevance: DART_RELEVANCE_DEFAULT, + String importUri, + protocol.ElementKind elemKind: null, + bool isDeprecated: false, + bool isPotential: false, + String elemFile, + int elemOffset}) { + CompletionSuggestion cs = + getSuggest(completion: completion, csKind: csKind, elemKind: elemKind); + if (cs == null) { + failedCompletion('expected $completion $csKind $elemKind', suggestions); + } + expect(cs.kind, equals(csKind)); + if (isDeprecated) { + expect(cs.relevance, equals(DART_RELEVANCE_LOW)); + } else { + expect(cs.relevance, equals(relevance)); + } + expect(cs.importUri, importUri); + expect(cs.selectionOffset, equals(completion.length)); + expect(cs.selectionLength, equals(0)); + expect(cs.isDeprecated, equals(isDeprecated)); + expect(cs.isPotential, equals(isPotential)); + if (cs.element != null) { + expect(cs.element.location, isNotNull); + expect(cs.element.location.file, isNotNull); + expect(cs.element.location.offset, isNotNull); + expect(cs.element.location.length, isNotNull); + expect(cs.element.location.startColumn, isNotNull); + expect(cs.element.location.startLine, isNotNull); + } + if (elemFile != null) { + expect(cs.element.location.file, elemFile); + } + if (elemOffset != null) { + expect(cs.element.location.offset, elemOffset); + } + return cs; + } + + /** + * Return a [Future] that completes with the containing library information + * after it is accessible via [context.getLibrariesContaining]. + */ + Future computeLibrariesContaining([int times = 200]) { + List libraries = context.getLibrariesContaining(testSource); + if (libraries.isNotEmpty) { + return new Future.value(libraries); + } + context.performAnalysisTask(); + // We use a delayed future to allow microtask events to finish. The + // Future.value or Future() constructors use scheduleMicrotask themselves and + // would therefore not wait for microtask callbacks that are scheduled after + // invoking this method. + return new Future.delayed( + Duration.ZERO, () => computeLibrariesContaining(times - 1)); + } + + Future computeSuggestions([int times = 200]) async { + CompletionRequestImpl baseRequest = new CompletionRequestImpl( + context, provider, testSource, completionOffset); + request = new DartCompletionRequestImpl.forRequest(baseRequest); + Completer> completer = + new Completer>(); + + // Request completions + contributor + .computeSuggestions(request) + .then((List computedSuggestions) { + completer.complete(computedSuggestions); + }); + + // Perform analysis until the suggestions have been computed + // or the max analysis cycles ([times]) has been reached + suggestions = await performAnalysis(times, completer); + expect(suggestions, isNotNull, reason: 'expected suggestions'); + } + + DartCompletionContributor createContributor(); + + void failedCompletion(String message, + [Iterable completions]) { + StringBuffer sb = new StringBuffer(message); + if (completions != null) { + sb.write('\n found'); + completions.toList() + ..sort(suggestionComparator) + ..forEach((CompletionSuggestion suggestion) { + sb.write('\n ${suggestion.completion} -> $suggestion'); + }); + } + fail(sb.toString()); + } + + CompletionSuggestion getSuggest( + {String completion: null, + CompletionSuggestionKind csKind: null, + protocol.ElementKind elemKind: null}) { + CompletionSuggestion cs; + if (suggestions != null) { + suggestions.forEach((CompletionSuggestion s) { + if (completion != null && completion != s.completion) { + return; + } + if (csKind != null && csKind != s.kind) { + return; + } + if (elemKind != null) { + protocol.Element element = s.element; + if (element == null || elemKind != element.kind) { + return; + } + } + if (cs == null) { + cs = s; + } else { + failedCompletion('expected exactly one $cs', + suggestions.where((s) => s.completion == completion)); + } + }); + } + return cs; + } + + Future performAnalysis(int times, Completer completer) { + if (completer.isCompleted) return completer.future; + if (times == 0 || context == null) return new Future.value(); + context.performAnalysisTask(); + // We use a delayed future to allow microtask events to finish. The + // Future.value or Future() constructors use scheduleMicrotask themselves and + // would therefore not wait for microtask callbacks that are scheduled after + // invoking this method. + return new Future.delayed( + Duration.ZERO, () => performAnalysis(times - 1, completer)); + } + + @override + void setUp() { + super.setUp(); + index = createLocalMemoryIndex(); + searchEngine = new SearchEngineImpl(index); + contributor = createContributor(); + } +} diff --git a/pkg/analysis_server/test/services/completion/dart/inherited_contributor_test.dart b/pkg/analysis_server/test/services/completion/dart/inherited_contributor_test.dart new file mode 100644 index 00000000000..cda1372bdef --- /dev/null +++ b/pkg/analysis_server/test/services/completion/dart/inherited_contributor_test.dart @@ -0,0 +1,122 @@ +// Copyright (c) 2015, 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. + +library test.services.completion.inherited_computer_test; + +import 'package:analysis_server/plugin/protocol/protocol.dart' + hide Element, ElementKind; +import 'package:analysis_server/src/provisional/completion/completion_dart.dart'; +import 'package:analysis_server/src/services/completion/dart/inherited_contributor.dart'; +import 'package:analysis_server/src/services/completion/dart_completion_manager.dart' + hide DartCompletionContributor; +import 'package:test_reflective_loader/test_reflective_loader.dart'; +import 'package:unittest/unittest.dart'; + +// import '../../../utils.dart'; +import 'dart_completion_contributor_test.dart'; + +main() { + // Revisit this contributor and these tests + // once DartChangeBuilder API has solidified. + // initializeTestEnvironment(); + // defineReflectiveTests(InheritedContributorTest); +} + +@reflectiveTest +class InheritedContributorTest extends DartCompletionContributorTest { + @override + DartCompletionContributor createContributor() { + return new InheritedContributor(); + } + + test_fromMultipleSuperclasses() async { + addTestSource(r''' +class A { + A suggested1(int x) => null; + B suggested2(String y) => null; +} +class B extends A { + B suggested2(String y) => null; + C suggested3([String z]) => null; +} +class C extends B { + sugg^ +} +'''); + await computeSuggestions(); + _assertOverride('''@override + A suggested1(int x) { + // TODO: implement suggested1 + return null; + }'''); + _assertOverride( + '''@override\n A suggested1(int x) {\n // TODO: implement suggested1\n return null;\n }'''); + _assertOverride( + '''@override\n B suggested2(String y) {\n // TODO: implement suggested2\n return null;\n }'''); + _assertOverride( + '''@override\n C suggested3([String z]) {\n // TODO: implement suggested3\n return null;\n }'''); + } + + test_fromPart() async { + addSource( + '/myLib.dart', + ''' +library myLib; +part '$testFile' +part '/otherPart.dart' +class A { + A suggested1(int x) => null; + B suggested2(String y) => null; +} +'''); + addSource( + '/otherPart.dart', + ''' +part of myLib; +class B extends A { + B suggested2(String y) => null; + C suggested3([String z]) => null; +} +'''); + addTestSource(r''' +part of myLib; +class C extends B { + sugg^ +} +'''); + // assume information for context.getLibrariesContaining has been cached + await computeLibrariesContaining(); + await computeSuggestions(); + _assertOverride('''@override + A suggested1(int x) { + // TODO: implement suggested1 + return null; + }'''); + _assertOverride( + '''@override\n A suggested1(int x) {\n // TODO: implement suggested1\n return null;\n }'''); + _assertOverride( + '''@override\n B suggested2(String y) {\n // TODO: implement suggested2\n return null;\n }'''); + _assertOverride( + '''@override\n C suggested3([String z]) {\n // TODO: implement suggested3\n return null;\n }'''); + } + + CompletionSuggestion _assertOverride(String completion) { + CompletionSuggestion cs = getSuggest( + completion: completion, + csKind: CompletionSuggestionKind.IDENTIFIER, + elemKind: null); + if (cs == null) { + failedCompletion('expected $completion', suggestions); + } + expect(cs.kind, equals(CompletionSuggestionKind.IDENTIFIER)); + expect(cs.relevance, equals(DART_RELEVANCE_HIGH)); + expect(cs.importUri, null); +// expect(cs.selectionOffset, equals(completion.length)); +// expect(cs.selectionLength, equals(0)); + expect(cs.isDeprecated, isFalse); + expect(cs.isPotential, isFalse); + expect(cs.element, isNotNull); + return cs; + } +} diff --git a/pkg/analysis_server/test/services/completion/dart/test_all.dart b/pkg/analysis_server/test/services/completion/dart/test_all.dart new file mode 100644 index 00000000000..a4c0e61a3e7 --- /dev/null +++ b/pkg/analysis_server/test/services/completion/dart/test_all.dart @@ -0,0 +1,18 @@ +// 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. + +library test.services.completion.dart; + +import 'package:unittest/unittest.dart'; +import 'inherited_contributor_test.dart' as inherited_contributor_test; + +import '../../../utils.dart'; + +/// Utility for manually running all tests. +main() { + initializeTestEnvironment(); + group('dart/completion', () { + inherited_contributor_test.main(); + }); +} diff --git a/pkg/analysis_server/test/services/completion/inherited_computer_test.dart b/pkg/analysis_server/test/services/completion/inherited_computer_test.dart deleted file mode 100644 index a50fa2f8cab..00000000000 --- a/pkg/analysis_server/test/services/completion/inherited_computer_test.dart +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2015, 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. - -library test.services.completion.inherited_computer_test; - -import 'package:analysis_server/plugin/protocol/protocol.dart'; -import 'package:analysis_server/src/services/completion/dart_completion_manager.dart'; -import 'package:analysis_server/src/services/completion/inherited_contributor.dart'; -import 'package:test_reflective_loader/test_reflective_loader.dart'; -import 'package:unittest/src/matcher/core_matchers.dart'; -import 'package:unittest/unittest.dart'; - -import '../../utils.dart'; -import 'completion_test_util.dart'; - -main() { - initializeTestEnvironment(); -// defineReflectiveTests(InheritedContributorTest); -} - -@reflectiveTest -class InheritedContributorTest extends AbstractCompletionTest { - @override - void setUpContributor() { - contributor = new NewCompletionWrapper(new InheritedContributor()); - } - - test_fromMultipleSuperclasses() { - addTestSource(r''' -class A { - notSuggested() => null; - A suggested1(int x) => null; - B suggested2(String y) => null; -} -class B extends A { - B suggested2(String y) => null; - C suggested3([String z]) => null; -} -class C extends B { - sugg^ -} -'''); - computeFast(); - return computeFull((bool result) { - _assertOverride( - '@override\n A suggested1(int x) {\n // TODO: implement suggested1\n return null;\n }'); - _assertOverride( - '''@override\n B suggested2(String y) {\n // TODO: implement suggested2\n return null;\n }'''); - _assertOverride( - '''@override\n C suggested3([String z]) {\n // TODO: implement suggested3\n return null;\n }'''); - assertNotSuggested( - '''@override\n notSuggested() {\n // TODO: implement notSuggested\n return null;\n }'''); - }); - } - - CompletionSuggestion _assertOverride(String completion) { - CompletionSuggestion cs = getSuggest( - completion: completion, - csKind: CompletionSuggestionKind.IDENTIFIER, - elemKind: null); - if (cs == null) { - failedCompletion('expected $completion', request.suggestions); - } - expect(cs.kind, equals(CompletionSuggestionKind.IDENTIFIER)); - expect(cs.relevance, equals(DART_RELEVANCE_HIGH)); - expect(cs.importUri, null); -// expect(cs.selectionOffset, equals(completion.length)); - expect(cs.selectionLength, equals(0)); - expect(cs.isDeprecated, isFalse); - expect(cs.isPotential, isFalse); - expect(cs.element, isNotNull); - return cs; - } -} diff --git a/pkg/analysis_server/test/services/completion/test_all.dart b/pkg/analysis_server/test/services/completion/test_all.dart index 69735da38e5..dba0885e411 100644 --- a/pkg/analysis_server/test/services/completion/test_all.dart +++ b/pkg/analysis_server/test/services/completion/test_all.dart @@ -13,8 +13,8 @@ import 'common_usage_computer_test.dart' as common_usage_computer_test; import 'completion_computer_test.dart' as completion_computer_test; import 'completion_manager_test.dart' as completion_manager_test; import 'completion_target_test.dart' as completion_target_test; +import 'dart/test_all.dart' as dart_contributor_tests; import 'imported_reference_contributor_test.dart' as imported_test; -import 'inherited_computer_test.dart' as inherited_computer_test; import 'keyword_contributor_test.dart' as keyword_test; import 'local_declaration_visitor_test.dart' as local_declaration_visitor_test; import 'local_reference_contributor_test.dart' @@ -33,8 +33,8 @@ main() { completion_computer_test.main(); completion_manager_test.main(); completion_target_test.main(); + dart_contributor_tests.main(); imported_test.main(); - inherited_computer_test.main(); invocation_test.main(); keyword_test.main(); local_declaration_visitor_test.main();