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 .
This commit is contained in:
danrubel
2015-11-23 12:50:12 -05:00
parent 6d69da223d
commit 38cec67d03
11 changed files with 640 additions and 396 deletions
@@ -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 <CompletionSuggestion>[];
/**
* A method or function called when the requested analysis has been performed.
*/
@@ -41,14 +49,15 @@ class AnalysisRequest<V> {
/**
* 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<CompletionSuggestion> computeSuggestions(CompletionRequest request);
Future<List<CompletionSuggestion>> computeSuggestions(
CompletionRequest request);
}
/**
@@ -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<CompletionSuggestion> computeSuggestions(CompletionRequest request) {
if (request is DartCompletionRequest) {
return internalComputeSuggestions(request);
}
AnalysisContext context = request.context;
Source source = request.source;
List<Source> 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<CompletionSuggestion> internalComputeSuggestions(
Future<List<CompletionSuggestion>> 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<CompilationUnit> resolveDeclarationsInScope();
}
@@ -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<CompilationUnit> 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<Source> 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<CompilationUnit>(
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;
}
}
@@ -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<List<CompletionSuggestion>> 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<String> memberNames = _computeMemberNames(map, classElem);
// Build suggestions
List<CompletionSuggestion> suggestions = <CompletionSuggestion>[];
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<VariableDeclaration> 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<String> _computeMemberNames(MemberMap map, ClassElement element) {
List<String> memberNames = <String>[];
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;
}
}
@@ -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<CompletionSuggestion> suggestions =
contributor.computeSuggestions(new OldRequestWrapper(request));
if (suggestions == null) {
return false;
}
for (CompletionSuggestion suggestion in suggestions) {
request.addSuggestion(suggestion);
}
return true;
}
@override
Future<bool> computeFull(DartCompletionRequest request) async {
List<CompletionSuggestion> 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';
}
@@ -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<CompletionSuggestion> 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<String> _computeMemberNames(
MemberMap map, ClassElement element, String prefix) {
List<String> memberNames = <String>[];
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<VariableDeclaration> 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<CompletionSuggestion> _suggestInheritedMembers(
DartCompletionRequest request,
SimpleIdentifier identifier,
ClassElement element) {
String name = identifier.name;
InheritanceManager manager = new InheritanceManager(element.library);
MemberMap map = manager.getMapOfMembersInheritedFromInterfaces(element);
List<String> memberNames = _computeMemberNames(map, element, name);
memberNames.sort();
List<CompletionSuggestion> suggestions = <CompletionSuggestion>[];
for (String memberName in memberNames) {
CompletionSuggestion suggestion =
_buildSuggestion(request.source, identifier, map.get(memberName));
if (suggestion != null) {
suggestions.add(suggestion);
}
}
return suggestions;
}
}
@@ -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<CompletionSuggestion> 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<Source> 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<List<CompletionSuggestion>> completer =
new Completer<List<CompletionSuggestion>>();
// Request completions
contributor
.computeSuggestions(request)
.then((List<CompletionSuggestion> 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<CompletionSuggestion> 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();
}
}
@@ -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;
}
}
@@ -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();
});
}
@@ -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;
}
}
@@ -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();