mirror of
https://github.com/dart-lang/sdk
synced 2024-09-19 15:01:29 +00:00
f46c9afc12
The Maps are all JSON, so they become `Map<Object?, Object?>`. Most other types get a `Object` or `Object?` type argument, and a few also get `dynamic`. Change-Id: I097318defed55360b5b0d910bd2d085a121e97b3 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/287673 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Samuel Rawlins <srawlins@google.com>
217 lines
7.3 KiB
Dart
217 lines
7.3 KiB
Dart
// 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:collection';
|
|
|
|
import 'package:analyzer_plugin/protocol/protocol_common.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
import 'domain_completion_util.dart';
|
|
|
|
/// A base class for classes containing completion tests.
|
|
class CompletionTestCase extends AbstractCompletionDomainTest {
|
|
static const String CURSOR_MARKER = '!';
|
|
|
|
List<String> get suggestedCompletions => suggestions
|
|
.map((CompletionSuggestion suggestion) => suggestion.completion)
|
|
.toList();
|
|
|
|
void assertHasCompletion(String completion,
|
|
{ElementKind? elementKind, bool? isDeprecated}) {
|
|
var expectedOffset = completion.indexOf(CURSOR_MARKER);
|
|
if (expectedOffset >= 0) {
|
|
if (completion.contains(CURSOR_MARKER, expectedOffset + 1)) {
|
|
fail(
|
|
"Invalid completion, contains multiple cursor positions: '$completion'");
|
|
}
|
|
completion = completion.replaceFirst(CURSOR_MARKER, '');
|
|
} else {
|
|
expectedOffset = completion.length;
|
|
}
|
|
CompletionSuggestion? matchingSuggestion;
|
|
for (var suggestion in suggestions) {
|
|
if (suggestion.completion == completion) {
|
|
if (matchingSuggestion == null) {
|
|
matchingSuggestion = suggestion;
|
|
} else {
|
|
// It is OK to have a class and its default constructor suggestions.
|
|
if (matchingSuggestion.element?.kind == ElementKind.CLASS &&
|
|
suggestion.element?.kind == ElementKind.CONSTRUCTOR ||
|
|
matchingSuggestion.element?.kind == ElementKind.CONSTRUCTOR &&
|
|
suggestion.element?.kind == ElementKind.CLASS) {
|
|
return;
|
|
}
|
|
fail(
|
|
"Expected exactly one '$completion' but found multiple:\n $suggestedCompletions");
|
|
}
|
|
}
|
|
}
|
|
if (matchingSuggestion == null) {
|
|
fail("Expected '$completion' but found none:\n $suggestedCompletions");
|
|
}
|
|
expect(matchingSuggestion.selectionOffset, equals(expectedOffset));
|
|
expect(matchingSuggestion.selectionLength, equals(0));
|
|
if (elementKind != null) {
|
|
expect(matchingSuggestion.element!.kind, elementKind);
|
|
}
|
|
if (isDeprecated != null) {
|
|
expect(matchingSuggestion.isDeprecated, isDeprecated);
|
|
}
|
|
}
|
|
|
|
void assertHasNoCompletion(String completion) {
|
|
if (suggestions.any((CompletionSuggestion suggestion) =>
|
|
suggestion.completion == completion)) {
|
|
fail(
|
|
"Did not expect completion '$completion' but found:\n $suggestedCompletions");
|
|
}
|
|
}
|
|
|
|
/// Discard any results that do not start with the characters the user has
|
|
/// "already typed".
|
|
void filterResults(String content) {
|
|
var charsAlreadyTyped =
|
|
content.substring(replacementOffset!, completionOffset).toLowerCase();
|
|
suggestions = suggestions
|
|
.where((CompletionSuggestion suggestion) =>
|
|
suggestion.completion.toLowerCase().startsWith(charsAlreadyTyped))
|
|
.toList();
|
|
}
|
|
|
|
Future<void> runTest(LocationSpec spec,
|
|
[Map<String, String>? extraFiles]) async {
|
|
await super.setUp();
|
|
|
|
try {
|
|
extraFiles?.forEach((String fileName, String content) {
|
|
newFile(fileName, content);
|
|
});
|
|
|
|
newFile(testFile.path, spec.source);
|
|
completionOffset = spec.testLocation;
|
|
await getSuggestions(
|
|
path: testFile.path,
|
|
completionOffset: completionOffset,
|
|
);
|
|
|
|
filterResults(spec.source);
|
|
for (var result in spec.positiveResults) {
|
|
assertHasCompletion(result);
|
|
}
|
|
for (var result in spec.negativeResults) {
|
|
assertHasNoCompletion(result);
|
|
}
|
|
} finally {
|
|
await super.tearDown();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A specification of the completion results expected at a given location.
|
|
class LocationSpec {
|
|
String id;
|
|
int testLocation = -1;
|
|
List<String> positiveResults = <String>[];
|
|
List<String> negativeResults = <String>[];
|
|
late String source;
|
|
|
|
LocationSpec(this.id);
|
|
|
|
/// Parse a set of tests from the given `originalSource`. Return a list of the
|
|
/// specifications that were parsed.
|
|
///
|
|
/// The source string has test locations embedded in it, which are identified
|
|
/// by '!X' where X is a single character. Each X is matched to positive or
|
|
/// negative results in the array of [validationStrings]. Validation strings
|
|
/// contain the name of a prediction with a two character prefix. The first
|
|
/// character of the prefix corresponds to an X in the [originalSource]. The
|
|
/// second character is either a '+' or a '-' indicating whether the string is
|
|
/// a positive or negative result. If logical not is needed in the source it
|
|
/// can be represented by '!!'.
|
|
///
|
|
/// The [originalSource] is the source for a test that contains test
|
|
/// locations. The [validationStrings] are the positive and negative
|
|
/// predictions.
|
|
static List<LocationSpec> from(
|
|
String originalSource, List<String> validationStrings) {
|
|
Map<String, LocationSpec> tests = HashMap<String, LocationSpec>();
|
|
var modifiedSource = originalSource;
|
|
var modifiedPosition = 0;
|
|
while (true) {
|
|
var index = modifiedSource.indexOf('!', modifiedPosition);
|
|
if (index < 0) {
|
|
break;
|
|
}
|
|
var n = 1; // only delete one char for double-bangs
|
|
var id = modifiedSource.substring(index + 1, index + 2);
|
|
if (id != '!') {
|
|
n = 2;
|
|
var test = LocationSpec(id);
|
|
tests[id] = test;
|
|
test.testLocation = index;
|
|
} else {
|
|
modifiedPosition = index + 1;
|
|
}
|
|
modifiedSource = modifiedSource.substring(0, index) +
|
|
modifiedSource.substring(index + n);
|
|
}
|
|
if (modifiedSource == originalSource) {
|
|
throw StateError('No tests in source: $originalSource');
|
|
}
|
|
for (var result in validationStrings) {
|
|
if (result.length < 3) {
|
|
throw StateError('Invalid location result: $result');
|
|
}
|
|
var id = result.substring(0, 1);
|
|
var sign = result.substring(1, 2);
|
|
var value = result.substring(2);
|
|
var test = tests[id];
|
|
if (test == null) {
|
|
throw StateError('Invalid location result id: $id for: $result');
|
|
}
|
|
test.source = modifiedSource;
|
|
if (sign == '+') {
|
|
test.positiveResults.add(value);
|
|
} else if (sign == '-') {
|
|
test.negativeResults.add(value);
|
|
} else {
|
|
var err = 'Invalid location result sign: $sign for: $result';
|
|
throw StateError(err);
|
|
}
|
|
}
|
|
var badPoints = <String>[];
|
|
var badResults = <String>[];
|
|
for (var test in tests.values) {
|
|
if (test.testLocation == -1) {
|
|
badPoints.add(test.id);
|
|
}
|
|
if (test.positiveResults.isEmpty && test.negativeResults.isEmpty) {
|
|
badResults.add(test.id);
|
|
}
|
|
}
|
|
if (!(badPoints.isEmpty && badResults.isEmpty)) {
|
|
var err = StringBuffer();
|
|
if (badPoints.isNotEmpty) {
|
|
err.write('No test location for tests:');
|
|
for (var ch in badPoints) {
|
|
err
|
|
..write(' ')
|
|
..write(ch);
|
|
}
|
|
err.write(' ');
|
|
}
|
|
if (badResults.isNotEmpty) {
|
|
err.write('No results for tests:');
|
|
for (var ch in badResults) {
|
|
err
|
|
..write(' ')
|
|
..write(ch);
|
|
}
|
|
}
|
|
throw StateError(err.toString());
|
|
}
|
|
return tests.values.toList();
|
|
}
|
|
}
|