blob: fc67b56303dce6f7a9040520460b7dfd461dca29 [file] [log] [blame]
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
import 'package:analysis_server/src/services/completion/completion_core.dart';
import 'package:analysis_server/src/services/completion/completion_performance.dart';
import 'package:analysis_server/src/services/completion/dart/completion_manager.dart'
show DartCompletionRequestImpl;
import 'package:analyzer/src/dart/analysis/driver.dart';
import 'package:analyzer/src/generated/parser.dart' as analyzer;
import 'package:analyzer_plugin/protocol/protocol_common.dart';
import 'package:test/test.dart';
import '../../../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;
int completionOffset;
int replacementOffset;
int replacementLength;
DartCompletionContributor 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;
/**
* Return `true` if contributors should suggest constructors in contexts where
* there is no `new` or `const` keyword.
*/
bool get suggestConstructorsWithoutNew => true;
bool get usingFastaParser => analyzer.Parser.useFasta;
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);
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,
String importUri,
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.importUri, importUri);
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, convertPath(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,
String importUri,
CompletionSuggestionKind kind: CompletionSuggestionKind.INVOCATION,
bool isDeprecated: false,
String elemFile,
String elemName,
int elemOffset}) {
CompletionSuggestion cs = assertSuggest(name,
csKind: kind,
relevance: relevance,
importUri: importUri,
isDeprecated: isDeprecated,
elemFile: elemFile,
elemKind: ElementKind.CLASS,
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,
String importUri,
String elementName,
int elemOffset,
String defaultArgListString: _UNCHECKED,
List<int> defaultArgumentListTextRanges}) {
CompletionSuggestion cs = assertSuggest(name,
relevance: relevance,
importUri: importUri,
elemKind: ElementKind.CONSTRUCTOR,
elemOffset: elemOffset,
defaultArgListString: defaultArgListString,
defaultArgumentListTextRanges: defaultArgumentListTextRanges);
Element element = cs.element;
expect(element, isNotNull);
expect(element.kind, equals(ElementKind.CONSTRUCTOR));
int index = name.indexOf('.');
elementName ??= index >= 0 ? name.substring(index + 1) : '';
expect(element.name, elementName);
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,
bool hasTypeBoost: false}) {
if (hasTypeBoost) {
relevance += DART_RELEVANCE_BOOST_TYPE;
}
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,
String importUri,
CompletionSuggestionKind kind: CompletionSuggestionKind.INVOCATION,
bool isDeprecated: false}) {
CompletionSuggestion cs = assertSuggest(name,
csKind: kind,
relevance: relevance,
importUri: importUri,
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 importUri,
String defaultArgListString: _UNCHECKED,
List<int> defaultArgumentListTextRanges}) {
CompletionSuggestion cs = assertSuggest(name,
csKind: kind,
relevance: relevance,
importUri: importUri,
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,
String importUri}) {
CompletionSuggestion cs = assertSuggest(name,
csKind: kind,
relevance: relevance,
importUri: importUri,
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,
String importUri,
CompletionSuggestionKind kind: CompletionSuggestionKind.INVOCATION,
bool isDeprecated: false}) {
CompletionSuggestion cs = assertSuggest(name,
csKind: kind,
relevance: relevance,
importUri: importUri,
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,
String importUri,
CompletionSuggestionKind kind: CompletionSuggestionKind.INVOCATION,
bool isDeprecated: false,
String defaultArgListString: _UNCHECKED,
List<int> defaultArgumentListTextRanges}) {
CompletionSuggestion cs = assertSuggest(name,
csKind: kind,
relevance: relevance,
importUri: importUri,
isDeprecated: isDeprecated,
defaultArgListString: defaultArgListString,
defaultArgumentListTextRanges: defaultArgumentListTextRanges);
expect(cs.declaringType, equals(declaringType));
expect(cs.returnType, 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 ?? 'dynamic');
assertHasParameterInfo(cs);
return cs;
}
CompletionSuggestion assertSuggestName(String name,
{int relevance: DART_RELEVANCE_DEFAULT,
String importUri,
CompletionSuggestionKind kind: CompletionSuggestionKind.IDENTIFIER,
bool isDeprecated: false}) {
CompletionSuggestion cs = assertSuggest(name,
csKind: kind,
relevance: relevance,
importUri: importUri,
isDeprecated: isDeprecated);
expect(cs.completion, equals(name));
expect(cs.element, isNull);
assertHasNoParameterInfo(cs);
return cs;
}
CompletionSuggestion assertSuggestSetter(String name,
{int relevance: DART_RELEVANCE_DEFAULT,
String importUri,
CompletionSuggestionKind kind: CompletionSuggestionKind.INVOCATION}) {
CompletionSuggestion cs = assertSuggest(name,
csKind: kind,
relevance: relevance,
importUri: importUri,
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,
String importUri}) {
CompletionSuggestion cs = assertSuggest(name,
csKind: kind, relevance: relevance, importUri: importUri);
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;
}
/**
* Return a [Future] that completes with the containing library information
* after it is accessible via [context.getLibrariesContaining].
*/
Future<void> computeLibrariesContaining([int times = 200]) {
return driver.getResult(testFile).then((result) => null);
}
Future computeSuggestions({int times = 200}) async {
AnalysisResult analysisResult =
await driver.getResult(convertPath(testFile));
CompletionRequestImpl baseRequest = new CompletionRequestImpl(
analysisResult, completionOffset, new CompletionPerformance());
// Build the request
var request = await DartCompletionRequestImpl.from(baseRequest);
var range = request.target.computeReplacementRange(request.offset);
replacementOffset = range.offset;
replacementLength = range.length;
// Request completions
suggestions = await contributor.computeSuggestions(request);
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,
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 {
// Await a microtask. Otherwise the futures are chained and would
// resolve linearly using up the stack.
await null;
if (completer.isCompleted) {
return completer.future;
}
// We use a delayed future to allow microtask events to finish. The
// Future.value or Future.microtask() constructors use scheduleMicrotask
// themselves and would therefore not wait for microtask callbacks that
// are scheduled after invoking this method.
return new Future(() => performAnalysis(times - 1, completer));
}
void resolveSource(String path, String content) {
addSource(path, content);
}
@override
void setUp() {
super.setUp();
testFile = resourceProvider.convertPath('/completionTest.dart');
contributor = createContributor();
}
}