a53b585498
* Add "usingFastaParser" accessor for Analyzer engine tests * Update Analyzer compile time error code tests * Add additional checks for async/await/yield as identifiers * Update built-in as type variable error message * Check for "this." in local declaration parameters * Remove unnecessary asserts and address comment in https://dart-review.googlesource.com/c/sdk/+/57022 Change-Id: I54b5ac22f912289dad6360646fae7eca717bbd98 Reviewed-on: https://dart-review.googlesource.com/57220 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Dan Rubel <danrubel@google.com>
588 lines
17 KiB
Dart
588 lines
17 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 'package:analyzer/dart/ast/ast.dart' show AstNode, SimpleIdentifier;
|
|
import 'package:analyzer/dart/element/element.dart';
|
|
import 'package:analyzer/dart/element/type.dart';
|
|
import 'package:analyzer/error/error.dart';
|
|
import 'package:analyzer/error/listener.dart';
|
|
import 'package:analyzer/exception/exception.dart';
|
|
import 'package:analyzer/src/dart/ast/utilities.dart';
|
|
import 'package:analyzer/src/generated/engine.dart';
|
|
import 'package:analyzer/src/generated/java_engine.dart';
|
|
import 'package:analyzer/src/generated/parser.dart';
|
|
import 'package:analyzer/src/generated/source.dart';
|
|
import 'package:plugin/manager.dart';
|
|
import 'package:plugin/plugin.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
import 'analysis_context_factory.dart';
|
|
|
|
/**
|
|
* The class `EngineTestCase` defines utility methods for making assertions.
|
|
*/
|
|
class EngineTestCase {
|
|
/**
|
|
* Flag indicating whether the fasta parser is being used.
|
|
*/
|
|
bool get usingFastaParser => Parser.useFasta;
|
|
|
|
/**
|
|
* Assert that the given collection has the same number of elements as the number of specified
|
|
* names, and that for each specified name, a corresponding element can be found in the given
|
|
* collection with that name.
|
|
*
|
|
* @param elements the elements
|
|
* @param names the names
|
|
*/
|
|
void assertNamedElements(List<Element> elements, List<String> names) {
|
|
for (String elemName in names) {
|
|
bool found = false;
|
|
for (Element elem in elements) {
|
|
if (elem.name == elemName) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
StringBuffer buffer = new StringBuffer();
|
|
buffer.write("Expected element named: ");
|
|
buffer.write(elemName);
|
|
buffer.write("\n but found: ");
|
|
for (Element elem in elements) {
|
|
buffer.write(elem.name);
|
|
buffer.write(", ");
|
|
}
|
|
fail(buffer.toString());
|
|
}
|
|
}
|
|
expect(elements, hasLength(names.length));
|
|
}
|
|
|
|
AnalysisContext createAnalysisContext() {
|
|
return AnalysisContextFactory.contextWithCore();
|
|
}
|
|
|
|
/**
|
|
* Return the getter in the given type with the given name. Inherited getters are ignored.
|
|
*
|
|
* @param type the type in which the getter is declared
|
|
* @param getterName the name of the getter to be returned
|
|
* @return the property accessor element representing the getter with the given name
|
|
*/
|
|
PropertyAccessorElement getGetter(InterfaceType type, String getterName) {
|
|
for (PropertyAccessorElement accessor in type.element.accessors) {
|
|
if (accessor.isGetter && accessor.name == getterName) {
|
|
return accessor;
|
|
}
|
|
}
|
|
fail("Could not find getter named $getterName in ${type.displayName}");
|
|
}
|
|
|
|
/**
|
|
* Return the method in the given type with the given name. Inherited methods are ignored.
|
|
*
|
|
* @param type the type in which the method is declared
|
|
* @param methodName the name of the method to be returned
|
|
* @return the method element representing the method with the given name
|
|
*/
|
|
MethodElement getMethod(InterfaceType type, String methodName) {
|
|
for (MethodElement method in type.element.methods) {
|
|
if (method.name == methodName) {
|
|
return method;
|
|
}
|
|
}
|
|
fail("Could not find method named $methodName in ${type.displayName}");
|
|
}
|
|
|
|
void setUp() {
|
|
List<Plugin> plugins = <Plugin>[];
|
|
plugins.addAll(AnalysisEngine.instance.requiredPlugins);
|
|
new ExtensionManager().processPlugins(plugins);
|
|
}
|
|
|
|
void tearDown() {}
|
|
|
|
/**
|
|
* Assert that the given object is an instance of the expected class.
|
|
*
|
|
* @param expectedClass the class that the object is expected to be an instance of
|
|
* @param object the object being tested
|
|
* @return the object that was being tested
|
|
* @throws Exception if the object is not an instance of the expected class
|
|
*/
|
|
static Object assertInstanceOf(
|
|
Predicate<Object> predicate, Type expectedClass, Object object) {
|
|
if (!predicate(object)) {
|
|
fail(
|
|
"Expected instance of $expectedClass, found ${object == null ? "null" : object.runtimeType}");
|
|
}
|
|
return object;
|
|
}
|
|
|
|
/**
|
|
* @return the [AstNode] with requested type at offset of the "prefix".
|
|
*/
|
|
static AstNode findNode(
|
|
AstNode root, String code, String prefix, Predicate<AstNode> predicate) {
|
|
int offset = code.indexOf(prefix);
|
|
if (offset == -1) {
|
|
throw new ArgumentError("Not found '$prefix'.");
|
|
}
|
|
AstNode node = new NodeLocator(offset).searchWithin(root);
|
|
return node.getAncestor(predicate);
|
|
}
|
|
|
|
/**
|
|
* Find the [SimpleIdentifier] with at offset of the "prefix".
|
|
*/
|
|
static SimpleIdentifier findSimpleIdentifier(
|
|
AstNode root, String code, String prefix) {
|
|
int offset = code.indexOf(prefix);
|
|
if (offset == -1) {
|
|
throw new ArgumentError("Not found '$prefix'.");
|
|
}
|
|
return new NodeLocator(offset).searchWithin(root);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A description of an error that is expected to be reported.
|
|
*/
|
|
class ExpectedError {
|
|
/**
|
|
* An empty array of error descriptors used when no errors are expected.
|
|
*/
|
|
static List<ExpectedError> NO_ERRORS = <ExpectedError>[];
|
|
|
|
/**
|
|
* The error code associated with the error.
|
|
*/
|
|
final ErrorCode code;
|
|
|
|
/**
|
|
* The offset of the beginning of the error's region.
|
|
*/
|
|
final int offset;
|
|
|
|
/**
|
|
* The offset of the beginning of the error's region.
|
|
*/
|
|
final int length;
|
|
|
|
/**
|
|
* Initialize a newly created error description.
|
|
*/
|
|
ExpectedError(this.code, this.offset, this.length);
|
|
}
|
|
|
|
/**
|
|
* An error listener that collects all of the errors passed to it for later
|
|
* examination.
|
|
*/
|
|
class GatheringErrorListener implements AnalysisErrorListener {
|
|
/**
|
|
* A flag indicating whether error ranges are to be compared when comparing
|
|
* expected and actual errors.
|
|
*/
|
|
final bool checkRanges;
|
|
|
|
/**
|
|
* A list containing the errors that were collected.
|
|
*/
|
|
List<AnalysisError> _errors = <AnalysisError>[];
|
|
|
|
/**
|
|
* A table mapping sources to the line information for the source.
|
|
*/
|
|
Map<Source, LineInfo> _lineInfoMap = <Source, LineInfo>{};
|
|
|
|
/**
|
|
* Initialize a newly created error listener to collect errors.
|
|
*/
|
|
GatheringErrorListener({this.checkRanges = Parser.useFasta});
|
|
|
|
/**
|
|
* Return the errors that were collected.
|
|
*/
|
|
List<AnalysisError> get errors => _errors;
|
|
|
|
/**
|
|
* Return `true` if at least one error has been gathered.
|
|
*/
|
|
bool get hasErrors => _errors.length > 0;
|
|
|
|
/**
|
|
* Add the given [errors] to this listener.
|
|
*/
|
|
void addAll(List<AnalysisError> errors) {
|
|
for (AnalysisError error in errors) {
|
|
onError(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add all of the errors recorded by the given [listener] to this listener.
|
|
*/
|
|
void addAll2(RecordingErrorListener listener) {
|
|
addAll(listener.errors);
|
|
}
|
|
|
|
/**
|
|
* Assert that the number of errors that have been gathered matches the number
|
|
* of [expectedErrors] and that they have the expected error codes and
|
|
* locations. The order in which the errors were gathered is ignored.
|
|
*/
|
|
void assertErrors(List<ExpectedError> expectedErrors) {
|
|
if (_errors.length != expectedErrors.length) {
|
|
_fail(expectedErrors);
|
|
}
|
|
List<ExpectedError> remainingErrors = expectedErrors.toList();
|
|
for (AnalysisError error in _errors) {
|
|
if (!_foundAndRemoved(remainingErrors, error)) {
|
|
_fail(expectedErrors);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Assert that the number of errors that have been gathered matches the number
|
|
* of [expectedErrorCodes] and that they have the expected error codes. The
|
|
* order in which the errors were gathered is ignored.
|
|
*/
|
|
void assertErrorsWithCodes(
|
|
[List<ErrorCode> expectedErrorCodes = const <ErrorCode>[]]) {
|
|
StringBuffer buffer = new StringBuffer();
|
|
//
|
|
// Compute the expected number of each type of error.
|
|
//
|
|
Map<ErrorCode, int> expectedCounts = <ErrorCode, int>{};
|
|
for (ErrorCode code in expectedErrorCodes) {
|
|
int count = expectedCounts[code];
|
|
if (count == null) {
|
|
count = 1;
|
|
} else {
|
|
count = count + 1;
|
|
}
|
|
expectedCounts[code] = count;
|
|
}
|
|
//
|
|
// Compute the actual number of each type of error.
|
|
//
|
|
Map<ErrorCode, List<AnalysisError>> errorsByCode =
|
|
<ErrorCode, List<AnalysisError>>{};
|
|
for (AnalysisError error in _errors) {
|
|
errorsByCode
|
|
.putIfAbsent(error.errorCode, () => <AnalysisError>[])
|
|
.add(error);
|
|
}
|
|
//
|
|
// Compare the expected and actual number of each type of error.
|
|
//
|
|
expectedCounts.forEach((ErrorCode code, int expectedCount) {
|
|
int actualCount;
|
|
List<AnalysisError> list = errorsByCode.remove(code);
|
|
if (list == null) {
|
|
actualCount = 0;
|
|
} else {
|
|
actualCount = list.length;
|
|
}
|
|
if (actualCount != expectedCount) {
|
|
if (buffer.length == 0) {
|
|
buffer.write("Expected ");
|
|
} else {
|
|
buffer.write("; ");
|
|
}
|
|
buffer.write(expectedCount);
|
|
buffer.write(" errors of type ");
|
|
buffer.write(code.uniqueName);
|
|
buffer.write(", found ");
|
|
buffer.write(actualCount);
|
|
}
|
|
});
|
|
//
|
|
// Check that there are no more errors in the actual-errors map,
|
|
// otherwise record message.
|
|
//
|
|
errorsByCode.forEach((ErrorCode code, List<AnalysisError> actualErrors) {
|
|
int actualCount = actualErrors.length;
|
|
if (buffer.length == 0) {
|
|
buffer.write("Expected ");
|
|
} else {
|
|
buffer.write("; ");
|
|
}
|
|
buffer.write("0 errors of type ");
|
|
buffer.write(code.uniqueName);
|
|
buffer.write(", found ");
|
|
buffer.write(actualCount);
|
|
buffer.write(" (");
|
|
for (int i = 0; i < actualErrors.length; i++) {
|
|
AnalysisError error = actualErrors[i];
|
|
if (i > 0) {
|
|
buffer.write(", ");
|
|
}
|
|
buffer.write(error.offset);
|
|
}
|
|
buffer.write(")");
|
|
});
|
|
if (buffer.length > 0) {
|
|
fail(buffer.toString());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Assert that the number of errors that have been gathered matches the number
|
|
* of [expectedSeverities] and that there are the same number of errors and
|
|
* warnings as specified by the argument. The order in which the errors were
|
|
* gathered is ignored.
|
|
*/
|
|
void assertErrorsWithSeverities(List<ErrorSeverity> expectedSeverities) {
|
|
int expectedErrorCount = 0;
|
|
int expectedWarningCount = 0;
|
|
for (ErrorSeverity severity in expectedSeverities) {
|
|
if (severity == ErrorSeverity.ERROR) {
|
|
expectedErrorCount++;
|
|
} else {
|
|
expectedWarningCount++;
|
|
}
|
|
}
|
|
int actualErrorCount = 0;
|
|
int actualWarningCount = 0;
|
|
for (AnalysisError error in _errors) {
|
|
if (error.errorCode.errorSeverity == ErrorSeverity.ERROR) {
|
|
actualErrorCount++;
|
|
} else {
|
|
actualWarningCount++;
|
|
}
|
|
}
|
|
if (expectedErrorCount != actualErrorCount ||
|
|
expectedWarningCount != actualWarningCount) {
|
|
fail(
|
|
"Expected $expectedErrorCount errors and $expectedWarningCount warnings, found $actualErrorCount errors and $actualWarningCount warnings");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Assert that no errors have been gathered.
|
|
*/
|
|
void assertNoErrors() {
|
|
assertErrors(ExpectedError.NO_ERRORS);
|
|
}
|
|
|
|
/**
|
|
* Return the line information associated with the given [source], or `null`
|
|
* if no line information has been associated with the source.
|
|
*/
|
|
LineInfo getLineInfo(Source source) => _lineInfoMap[source];
|
|
|
|
/**
|
|
* Return `true` if an error with the given [errorCode] has been gathered.
|
|
*/
|
|
bool hasError(ErrorCode errorCode) {
|
|
for (AnalysisError error in _errors) {
|
|
if (identical(error.errorCode, errorCode)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@override
|
|
void onError(AnalysisError error) {
|
|
_errors.add(error);
|
|
}
|
|
|
|
/**
|
|
* Set the line information associated with the given [source] to the given
|
|
* list of [lineStarts].
|
|
*/
|
|
void setLineInfo(Source source, List<int> lineStarts) {
|
|
_lineInfoMap[source] = new LineInfo(lineStarts);
|
|
}
|
|
|
|
/**
|
|
* Return `true` if the [actualError] matches the [expectedError].
|
|
*/
|
|
bool _equalErrors(ExpectedError expectedError, AnalysisError actualError) {
|
|
if (!identical(expectedError.code, actualError.errorCode)) {
|
|
return false;
|
|
} else if (!checkRanges) {
|
|
return true;
|
|
}
|
|
return expectedError.offset == actualError.offset &&
|
|
expectedError.length == actualError.length;
|
|
}
|
|
|
|
/**
|
|
* Assert that the number of errors that have been gathered matches the number
|
|
* of [expectedErrors] and that they have the expected error codes. The order
|
|
* in which the errors were gathered is ignored.
|
|
*/
|
|
void _fail(List<ExpectedError> expectedErrors) {
|
|
StringBuffer buffer = new StringBuffer();
|
|
buffer.write("Expected ");
|
|
buffer.write(expectedErrors.length);
|
|
buffer.write(" errors:");
|
|
for (ExpectedError error in expectedErrors) {
|
|
buffer.writeln();
|
|
int offset = error.offset;
|
|
buffer.write(' ${error.code} ($offset..${offset + error.length})');
|
|
}
|
|
buffer.writeln();
|
|
buffer.write("found ");
|
|
buffer.write(_errors.length);
|
|
buffer.write(" errors:");
|
|
for (AnalysisError error in _errors) {
|
|
buffer.writeln();
|
|
int offset = error.offset;
|
|
buffer.write(' ${error.errorCode} '
|
|
'($offset..${offset + error.length}): ${error.message}');
|
|
}
|
|
fail(buffer.toString());
|
|
}
|
|
|
|
/**
|
|
* Search through the given list of [errors] for an error that is equal to the
|
|
* [targetError]. If one is found, remove it from the list and return `true`,
|
|
* otherwise return `false` without modifying the list.
|
|
*/
|
|
bool _foundAndRemoved(List<ExpectedError> errors, AnalysisError targetError) {
|
|
for (ExpectedError error in errors) {
|
|
if (_equalErrors(error, targetError)) {
|
|
errors.remove(error);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Instances of the class [TestLogger] implement a logger that can be used by
|
|
* tests.
|
|
*/
|
|
class TestLogger implements Logger {
|
|
/**
|
|
* All logged messages.
|
|
*/
|
|
List<String> log = [];
|
|
|
|
@override
|
|
void logError(String message, [CaughtException exception]) {
|
|
log.add("error: $message");
|
|
}
|
|
|
|
@override
|
|
void logInformation(String message, [CaughtException exception]) {
|
|
log.add("info: $message");
|
|
}
|
|
}
|
|
|
|
class TestSource extends Source {
|
|
String _name;
|
|
String _contents;
|
|
int _modificationStamp = 0;
|
|
bool exists2 = true;
|
|
|
|
/**
|
|
* A flag indicating whether an exception should be generated when an attempt
|
|
* is made to access the contents of this source.
|
|
*/
|
|
bool generateExceptionOnRead = false;
|
|
|
|
/**
|
|
* The number of times that the contents of this source have been requested.
|
|
*/
|
|
int readCount = 0;
|
|
|
|
TestSource([this._name = '/test.dart', this._contents]);
|
|
|
|
TimestampedData<String> get contents {
|
|
readCount++;
|
|
if (generateExceptionOnRead) {
|
|
String msg = "I/O Exception while getting the contents of " + _name;
|
|
throw new Exception(msg);
|
|
}
|
|
return new TimestampedData<String>(0, _contents);
|
|
}
|
|
|
|
String get encoding => _name;
|
|
|
|
String get fullName {
|
|
return _name;
|
|
}
|
|
|
|
int get hashCode => 0;
|
|
|
|
bool get isInSystemLibrary {
|
|
return false;
|
|
}
|
|
|
|
@override
|
|
int get modificationStamp =>
|
|
generateExceptionOnRead ? -1 : _modificationStamp;
|
|
|
|
String get shortName {
|
|
return _name;
|
|
}
|
|
|
|
Uri get uri => new Uri.file(_name);
|
|
|
|
UriKind get uriKind {
|
|
throw new UnsupportedError('uriKind');
|
|
}
|
|
|
|
bool operator ==(Object other) {
|
|
if (other is TestSource) {
|
|
return other._name == _name;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool exists() => exists2;
|
|
void getContentsToReceiver(Source_ContentReceiver receiver) {
|
|
throw new UnsupportedError('getContentsToReceiver');
|
|
}
|
|
|
|
Source resolve(String uri) {
|
|
throw new UnsupportedError('resolve');
|
|
}
|
|
|
|
void setContents(String value) {
|
|
generateExceptionOnRead = false;
|
|
_modificationStamp = new DateTime.now().millisecondsSinceEpoch;
|
|
_contents = value;
|
|
}
|
|
|
|
@override
|
|
String toString() => '$_name';
|
|
}
|
|
|
|
class TestSourceWithUri extends TestSource {
|
|
final Uri uri;
|
|
|
|
TestSourceWithUri(String path, this.uri, [String content])
|
|
: super(path, content);
|
|
|
|
String get encoding => uri.toString();
|
|
|
|
UriKind get uriKind {
|
|
if (uri == null) {
|
|
return UriKind.FILE_URI;
|
|
} else if (uri.scheme == 'dart') {
|
|
return UriKind.DART_URI;
|
|
} else if (uri.scheme == 'package') {
|
|
return UriKind.PACKAGE_URI;
|
|
}
|
|
return UriKind.FILE_URI;
|
|
}
|
|
|
|
bool operator ==(Object other) {
|
|
if (other is TestSource) {
|
|
return other.uri == uri;
|
|
}
|
|
return false;
|
|
}
|
|
}
|