blob: d7a9ec28bb139d953da28e76aafccac1cd81aa5a [file] [log] [blame]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
import 'package:analyzer_plugin/src/utilities/completion/completion_core.dart';
import 'package:analyzer_plugin/src/utilities/completion/completion_target.dart';
import 'package:analyzer_plugin/utilities/completion/completion_core.dart';
import 'package:analyzer_plugin/utilities/completion/relevance.dart';
import 'package:test/test.dart';
import '../../support/abstract_context.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 {
static const String _UNCHECKED = '__UNCHECKED__';
String testFile;
Source testSource;
int completionOffset;
int replacementOffset;
int replacementLength;
CompletionContributor contributor;
DartCompletionRequest request;
List<CompletionSuggestion> suggestions;
/**
* If `true` and `null` is specified as the suggestion's expected returnType
* then the actual suggestion is expected to have a `dynamic` returnType.
* Newer tests return `false` so that they can distinguish between
* `dynamic` and `null`.
* Eventually all tests should be converted and this getter removed.
*/
bool get isNullExpectedReturnTypeConsideredDynamic => true;
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 assertHasNoParameterInfo(CompletionSuggestion suggestion) {
expect(suggestion.parameterNames, isNull);
expect(suggestion.parameterTypes, isNull);
expect(suggestion.requiredParameterCount, isNull);
expect(suggestion.hasNamedParameters, isNull);
}
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 assertNoSuggestions({CompletionSuggestionKind kind: null}) {
if (kind == null) {
if (suggestions.length > 0) {
failedCompletion('Expected no suggestions', suggestions);
}
return;
}
CompletionSuggestion suggestion = suggestions.firstWhere(
(CompletionSuggestion cs) => cs.kind == kind,
orElse: () => null);
if (suggestion != null) {
failedCompletion('did not expect completion: $completion\n $suggestion');
}
}
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,
ElementKind elemKind: null,
bool isDeprecated: false,
bool isPotential: false,
String elemFile,
int elemOffset,
int selectionOffset,
String paramName,
String paramType,
String defaultArgListString: _UNCHECKED,
List<int> defaultArgumentListTextRanges}) {
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), reason: completion);
}
expect(cs.selectionOffset, equals(selectionOffset ?? 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);
}
if (paramName != null) {
expect(cs.parameterName, paramName);
}
if (paramType != null) {
expect(cs.parameterType, paramType);
}
if (defaultArgListString != _UNCHECKED) {
expect(cs.defaultArgumentListString, defaultArgListString);
}
if (defaultArgumentListTextRanges != null) {
expect(cs.defaultArgumentListTextRanges, defaultArgumentListTextRanges);
}
return cs;
}
CompletionSuggestion assertSuggestClass(String name,
{int relevance: DART_RELEVANCE_DEFAULT,
CompletionSuggestionKind kind: CompletionSuggestionKind.INVOCATION,
bool isDeprecated: false,
String elemFile,
String elemName,
int elemOffset}) {
CompletionSuggestion cs = assertSuggest(name,
csKind: kind,
relevance: relevance,
isDeprecated: isDeprecated,
elemFile: elemFile,
elemOffset: elemOffset);
Element element = cs.element;
expect(element, isNotNull);
expect(element.kind, equals(ElementKind.CLASS));
expect(element.name, equals(elemName ?? name));
expect(element.parameters, isNull);
expect(element.returnType, isNull);
assertHasNoParameterInfo(cs);
return cs;
}
CompletionSuggestion assertSuggestClassTypeAlias(String name,
{int relevance: DART_RELEVANCE_DEFAULT,
CompletionSuggestionKind kind: CompletionSuggestionKind.INVOCATION}) {
CompletionSuggestion cs =
assertSuggest(name, csKind: kind, relevance: relevance);
Element element = cs.element;
expect(element, isNotNull);
expect(element.kind, equals(ElementKind.CLASS_TYPE_ALIAS));
expect(element.name, equals(name));
expect(element.parameters, isNull);
expect(element.returnType, isNull);
assertHasNoParameterInfo(cs);
return cs;
}
CompletionSuggestion assertSuggestConstructor(String name,
{int relevance: DART_RELEVANCE_DEFAULT,
int elemOffset,
String defaultArgListString: _UNCHECKED,
List<int> defaultArgumentListTextRanges}) {
CompletionSuggestion cs = assertSuggest(name,
relevance: relevance,
elemOffset: elemOffset,
defaultArgListString: defaultArgListString,
defaultArgumentListTextRanges: defaultArgumentListTextRanges);
Element element = cs.element;
expect(element, isNotNull);
expect(element.kind, equals(ElementKind.CONSTRUCTOR));
int index = name.indexOf('.');
expect(element.name, index >= 0 ? name.substring(index + 1) : '');
return cs;
}
CompletionSuggestion assertSuggestEnum(String completion,
{bool isDeprecated: false}) {
CompletionSuggestion suggestion =
assertSuggest(completion, isDeprecated: isDeprecated);
expect(suggestion.isDeprecated, isDeprecated);
expect(suggestion.element.kind, ElementKind.ENUM);
return suggestion;
}
CompletionSuggestion assertSuggestEnumConst(String completion,
{int relevance: DART_RELEVANCE_DEFAULT, bool isDeprecated: false}) {
CompletionSuggestion suggestion = assertSuggest(completion,
relevance: relevance, isDeprecated: isDeprecated);
expect(suggestion.completion, completion);
expect(suggestion.isDeprecated, isDeprecated);
expect(suggestion.element.kind, ElementKind.ENUM_CONSTANT);
return suggestion;
}
CompletionSuggestion assertSuggestField(String name, String type,
{int relevance: DART_RELEVANCE_DEFAULT,
CompletionSuggestionKind kind: CompletionSuggestionKind.INVOCATION,
bool isDeprecated: false}) {
CompletionSuggestion cs = assertSuggest(name,
csKind: kind,
relevance: relevance,
elemKind: ElementKind.FIELD,
isDeprecated: isDeprecated);
// The returnType represents the type of a field
expect(cs.returnType, type != null ? type : 'dynamic');
Element element = cs.element;
expect(element, isNotNull);
expect(element.kind, equals(ElementKind.FIELD));
expect(element.name, equals(name));
expect(element.parameters, isNull);
// The returnType represents the type of a field
expect(element.returnType, type != null ? type : 'dynamic');
assertHasNoParameterInfo(cs);
return cs;
}
CompletionSuggestion assertSuggestFunction(String name, String returnType,
{CompletionSuggestionKind kind: CompletionSuggestionKind.INVOCATION,
bool isDeprecated: false,
int relevance: DART_RELEVANCE_DEFAULT,
String defaultArgListString: _UNCHECKED,
List<int> defaultArgumentListTextRanges}) {
CompletionSuggestion cs = assertSuggest(name,
csKind: kind,
relevance: relevance,
isDeprecated: isDeprecated,
defaultArgListString: defaultArgListString,
defaultArgumentListTextRanges: defaultArgumentListTextRanges);
if (returnType != null) {
expect(cs.returnType, returnType);
} else if (isNullExpectedReturnTypeConsideredDynamic) {
expect(cs.returnType, 'dynamic');
}
Element element = cs.element;
expect(element, isNotNull);
expect(element.kind, equals(ElementKind.FUNCTION));
expect(element.name, equals(name));
expect(element.isDeprecated, equals(isDeprecated));
String param = element.parameters;
expect(param, isNotNull);
expect(param[0], equals('('));
expect(param[param.length - 1], equals(')'));
if (returnType != null) {
expect(element.returnType, returnType);
} else if (isNullExpectedReturnTypeConsideredDynamic) {
expect(element.returnType, 'dynamic');
}
assertHasParameterInfo(cs);
return cs;
}
CompletionSuggestion assertSuggestFunctionTypeAlias(
String name, String returnType,
{bool isDeprecated: false,
int relevance: DART_RELEVANCE_DEFAULT,
CompletionSuggestionKind kind: CompletionSuggestionKind.INVOCATION}) {
CompletionSuggestion cs = assertSuggest(name,
csKind: kind, relevance: relevance, isDeprecated: isDeprecated);
if (returnType != null) {
expect(cs.returnType, returnType);
} else if (isNullExpectedReturnTypeConsideredDynamic) {
expect(cs.returnType, 'dynamic');
} else {
expect(cs.returnType, isNull);
}
Element element = cs.element;
expect(element, isNotNull);
expect(element.kind, equals(ElementKind.FUNCTION_TYPE_ALIAS));
expect(element.name, equals(name));
expect(element.isDeprecated, equals(isDeprecated));
// TODO (danrubel) Determine why params are null
// String param = element.parameters;
// expect(param, isNotNull);
// expect(param[0], equals('('));
// expect(param[param.length - 1], equals(')'));
expect(element.returnType,
equals(returnType != null ? returnType : 'dynamic'));
// TODO (danrubel) Determine why param info is missing
// assertHasParameterInfo(cs);
return cs;
}
CompletionSuggestion assertSuggestGetter(String name, String returnType,
{int relevance: DART_RELEVANCE_DEFAULT,
CompletionSuggestionKind kind: CompletionSuggestionKind.INVOCATION,
bool isDeprecated: false}) {
CompletionSuggestion cs = assertSuggest(name,
csKind: kind,
relevance: relevance,
elemKind: ElementKind.GETTER,
isDeprecated: isDeprecated);
expect(cs.returnType, returnType != null ? returnType : 'dynamic');
Element element = cs.element;
expect(element, isNotNull);
expect(element.kind, equals(ElementKind.GETTER));
expect(element.name, equals(name));
expect(element.parameters, isNull);
expect(element.returnType,
equals(returnType != null ? returnType : 'dynamic'));
assertHasNoParameterInfo(cs);
return cs;
}
CompletionSuggestion assertSuggestMethod(
String name, String declaringType, String returnType,
{int relevance: DART_RELEVANCE_DEFAULT,
CompletionSuggestionKind kind: CompletionSuggestionKind.INVOCATION,
bool isDeprecated: false,
String defaultArgListString: _UNCHECKED,
List<int> defaultArgumentListTextRanges}) {
CompletionSuggestion cs = assertSuggest(name,
csKind: kind,
relevance: relevance,
isDeprecated: isDeprecated,
defaultArgListString: defaultArgListString,
defaultArgumentListTextRanges: defaultArgumentListTextRanges);
expect(cs.declaringType, equals(declaringType));
expect(cs.returnType, returnType != null ? returnType : 'dynamic');
Element element = cs.element;
expect(element, isNotNull);
expect(element.kind, equals(ElementKind.METHOD));
expect(element.name, equals(name));
String param = element.parameters;
expect(param, isNotNull);
expect(param[0], equals('('));
expect(param[param.length - 1], equals(')'));
expect(element.returnType, returnType != null ? returnType : 'dynamic');
assertHasParameterInfo(cs);
return cs;
}
CompletionSuggestion assertSuggestName(String name,
{int relevance: DART_RELEVANCE_DEFAULT,
CompletionSuggestionKind kind: CompletionSuggestionKind.IDENTIFIER,
bool isDeprecated: false}) {
CompletionSuggestion cs = assertSuggest(name,
csKind: kind, relevance: relevance, isDeprecated: isDeprecated);
expect(cs.completion, equals(name));
expect(cs.element, isNull);
assertHasNoParameterInfo(cs);
return cs;
}
CompletionSuggestion assertSuggestSetter(String name,
{int relevance: DART_RELEVANCE_DEFAULT,
CompletionSuggestionKind kind: CompletionSuggestionKind.INVOCATION}) {
CompletionSuggestion cs = assertSuggest(name,
csKind: kind, relevance: relevance, elemKind: ElementKind.SETTER);
Element element = cs.element;
expect(element, isNotNull);
expect(element.kind, equals(ElementKind.SETTER));
expect(element.name, equals(name));
// TODO (danrubel) assert setter param
//expect(element.parameters, isNull);
// TODO (danrubel) it would be better if this was always null
if (element.returnType != null) {
expect(element.returnType, 'dynamic');
}
assertHasNoParameterInfo(cs);
return cs;
}
CompletionSuggestion assertSuggestTopLevelVar(String name, String returnType,
{int relevance: DART_RELEVANCE_DEFAULT,
CompletionSuggestionKind kind: CompletionSuggestionKind.INVOCATION}) {
CompletionSuggestion cs =
assertSuggest(name, csKind: kind, relevance: relevance);
if (returnType != null) {
expect(cs.returnType, returnType);
} else if (isNullExpectedReturnTypeConsideredDynamic) {
expect(cs.returnType, 'dynamic');
}
Element element = cs.element;
expect(element, isNotNull);
expect(element.kind, equals(ElementKind.TOP_LEVEL_VARIABLE));
expect(element.name, equals(name));
expect(element.parameters, isNull);
if (returnType != null) {
expect(element.returnType, returnType);
} else if (isNullExpectedReturnTypeConsideredDynamic) {
expect(element.returnType, 'dynamic');
}
assertHasNoParameterInfo(cs);
return cs;
}
Future<void> computeLibrariesContaining() {
return driver.getResult(testFile).then((result) => null);
}
Future computeSuggestions() async {
ResolvedUnitResult result = await driver.getResult(testFile);
testSource = result.unit.declaredElement.source;
request = new DartCompletionRequestImpl(
resourceProvider, completionOffset, result);
CompletionTarget target =
new CompletionTarget.forOffset(request.result.unit, request.offset);
var range = target.computeReplacementRange(request.offset);
replacementOffset = range.offset;
replacementLength = range.length;
// Request completions
CompletionCollectorImpl collector = new CompletionCollectorImpl();
await contributor.computeSuggestions(request, collector);
suggestions = collector.suggestions;
expect(suggestions, isNotNull, reason: 'expected suggestions');
}
CompletionContributor 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,
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) {
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<E> performAnalysis<E>(int times, Completer<E> completer) async {
if (completer.isCompleted) {
return completer.future;
}
// 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));
}
void resolveSource(String path, String content) {
addSource(path, content);
}
@override
void setUp() {
super.setUp();
testFile = convertPath('/home/test/lib/test.dart');
contributor = createContributor();
}
}