blob: 5cd095d3e890239687901801b68b666a82c5ec21 [file] [log] [blame]
// Copyright (c) 2016, 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 analyzer.test.generated.resolver_test_case;
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/file_system/memory_file_system.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/java_engine.dart';
import 'package:analyzer/src/generated/resolver.dart';
import 'package:analyzer/src/generated/source_io.dart';
import 'package:analyzer/src/generated/testing/ast_test_factory.dart';
import 'package:analyzer/src/generated/testing/element_factory.dart';
import 'package:test/test.dart';
import 'analysis_context_factory.dart';
import 'test_support.dart';
/**
* An AST visitor used to verify that all of the nodes in an AST structure that
* should have been resolved were resolved.
*/
class ResolutionVerifier extends RecursiveAstVisitor<Object> {
/**
* A set containing nodes that are known to not be resolvable and should
* therefore not cause the test to fail.
*/
final Set<AstNode> _knownExceptions;
/**
* A list containing all of the AST nodes that were not resolved.
*/
List<AstNode> _unresolvedNodes = new List<AstNode>();
/**
* A list containing all of the AST nodes that were resolved to an element of
* the wrong type.
*/
List<AstNode> _wrongTypedNodes = new List<AstNode>();
/**
* Initialize a newly created verifier to verify that all of the identifiers
* in the visited AST structures that are expected to have been resolved have
* an element associated with them. Nodes in the set of [_knownExceptions] are
* not expected to have been resolved, even if they normally would have been
* expected to have been resolved.
*/
ResolutionVerifier([this._knownExceptions]);
/**
* Assert that all of the visited identifiers were resolved.
*/
void assertResolved() {
if (!_unresolvedNodes.isEmpty || !_wrongTypedNodes.isEmpty) {
StringBuffer buffer = new StringBuffer();
if (!_unresolvedNodes.isEmpty) {
buffer.write("Failed to resolve ");
buffer.write(_unresolvedNodes.length);
buffer.writeln(" nodes:");
_printNodes(buffer, _unresolvedNodes);
}
if (!_wrongTypedNodes.isEmpty) {
buffer.write("Resolved ");
buffer.write(_wrongTypedNodes.length);
buffer.writeln(" to the wrong type of element:");
_printNodes(buffer, _wrongTypedNodes);
}
fail(buffer.toString());
}
}
@override
Object visitAnnotation(Annotation node) {
node.visitChildren(this);
ElementAnnotation elementAnnotation = node.elementAnnotation;
if (elementAnnotation == null) {
if (_knownExceptions == null || !_knownExceptions.contains(node)) {
_unresolvedNodes.add(node);
}
} else if (elementAnnotation is! ElementAnnotation) {
_wrongTypedNodes.add(node);
}
return null;
}
@override
Object visitBinaryExpression(BinaryExpression node) {
node.visitChildren(this);
if (!node.operator.isUserDefinableOperator) {
return null;
}
DartType operandType = node.leftOperand.staticType;
if (operandType == null || operandType.isDynamic) {
return null;
}
return _checkResolved(
node, node.staticElement, (node) => node is MethodElement);
}
@override
Object visitCommentReference(CommentReference node) => null;
@override
Object visitCompilationUnit(CompilationUnit node) {
node.visitChildren(this);
return _checkResolved(
node, node.element, (node) => node is CompilationUnitElement);
}
@override
Object visitExportDirective(ExportDirective node) =>
_checkResolved(node, node.element, (node) => node is ExportElement);
@override
Object visitFunctionDeclaration(FunctionDeclaration node) {
node.visitChildren(this);
if (node.element is LibraryElement) {
_wrongTypedNodes.add(node);
}
return null;
}
@override
Object visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
node.visitChildren(this);
// TODO(brianwilkerson) If we start resolving function expressions, then
// conditionally check to see whether the node was resolved correctly.
return null;
//checkResolved(node, node.getElement(), FunctionElement.class);
}
@override
Object visitImportDirective(ImportDirective node) {
// Not sure how to test the combinators given that it isn't an error if the
// names are not defined.
_checkResolved(node, node.element, (node) => node is ImportElement);
SimpleIdentifier prefix = node.prefix;
if (prefix == null) {
return null;
}
return _checkResolved(
prefix, prefix.staticElement, (node) => node is PrefixElement);
}
@override
Object visitIndexExpression(IndexExpression node) {
node.visitChildren(this);
DartType targetType = node.realTarget.staticType;
if (targetType == null || targetType.isDynamic) {
return null;
}
return _checkResolved(
node, node.staticElement, (node) => node is MethodElement);
}
@override
Object visitLibraryDirective(LibraryDirective node) =>
_checkResolved(node, node.element, (node) => node is LibraryElement);
@override
Object visitNamedExpression(NamedExpression node) =>
node.expression.accept(this);
@override
Object visitPartDirective(PartDirective node) => _checkResolved(
node, node.element, (node) => node is CompilationUnitElement);
@override
Object visitPartOfDirective(PartOfDirective node) =>
_checkResolved(node, node.element, (node) => node is LibraryElement);
@override
Object visitPostfixExpression(PostfixExpression node) {
node.visitChildren(this);
if (!node.operator.isUserDefinableOperator) {
return null;
}
DartType operandType = node.operand.staticType;
if (operandType == null || operandType.isDynamic) {
return null;
}
return _checkResolved(
node, node.staticElement, (node) => node is MethodElement);
}
@override
Object visitPrefixedIdentifier(PrefixedIdentifier node) {
SimpleIdentifier prefix = node.prefix;
prefix.accept(this);
DartType prefixType = prefix.staticType;
if (prefixType == null || prefixType.isDynamic) {
return null;
}
return _checkResolved(node, node.staticElement, null);
}
@override
Object visitPrefixExpression(PrefixExpression node) {
node.visitChildren(this);
if (!node.operator.isUserDefinableOperator) {
return null;
}
DartType operandType = node.operand.staticType;
if (operandType == null || operandType.isDynamic) {
return null;
}
return _checkResolved(
node, node.staticElement, (node) => node is MethodElement);
}
@override
Object visitPropertyAccess(PropertyAccess node) {
Expression target = node.realTarget;
target.accept(this);
DartType targetType = target.staticType;
if (targetType == null || targetType.isDynamic) {
return null;
}
return node.propertyName.accept(this);
}
@override
Object visitSimpleIdentifier(SimpleIdentifier node) {
if (node.name == "void") {
return null;
}
if (node.staticType != null &&
node.staticType.isDynamic &&
node.staticElement == null) {
return null;
}
AstNode parent = node.parent;
if (parent is MethodInvocation) {
MethodInvocation invocation = parent;
if (identical(invocation.methodName, node)) {
Expression target = invocation.realTarget;
DartType targetType = target == null ? null : target.staticType;
if (targetType == null || targetType.isDynamic) {
return null;
}
}
}
return _checkResolved(node, node.staticElement, null);
}
Object _checkResolved(
AstNode node, Element element, Predicate<Element> predicate) {
if (element == null) {
if (_knownExceptions == null || !_knownExceptions.contains(node)) {
_unresolvedNodes.add(node);
}
} else if (predicate != null) {
if (!predicate(element)) {
_wrongTypedNodes.add(node);
}
}
return null;
}
String _getFileName(AstNode node) {
// TODO (jwren) there are two copies of this method, one here and one in
// StaticTypeVerifier, they should be resolved into a single method
if (node != null) {
AstNode root = node.root;
if (root is CompilationUnit) {
CompilationUnit rootCU = root;
if (rootCU.element != null) {
return rootCU.element.source.fullName;
} else {
return "<unknown file- CompilationUnit.getElement() returned null>";
}
} else {
return "<unknown file- CompilationUnit.getRoot() is not a CompilationUnit>";
}
}
return "<unknown file- ASTNode is null>";
}
void _printNodes(StringBuffer buffer, List<AstNode> nodes) {
for (AstNode identifier in nodes) {
buffer.write(" ");
buffer.write(identifier.toString());
buffer.write(" (");
buffer.write(_getFileName(identifier));
buffer.write(" : ");
buffer.write(identifier.offset);
buffer.writeln(")");
}
}
}
class ResolverTestCase extends EngineTestCase {
/**
* The resource provider used by the test case.
*/
MemoryResourceProvider resourceProvider = new MemoryResourceProvider();
/**
* The analysis context used to parse the compilation units being resolved.
*/
InternalAnalysisContext analysisContext2;
/**
* Specifies if [assertErrors] should check for [HintCode.UNUSED_ELEMENT] and
* [HintCode.UNUSED_FIELD].
*/
bool enableUnusedElement = false;
/**
* Specifies if [assertErrors] should check for [HintCode.UNUSED_LOCAL_VARIABLE].
*/
bool enableUnusedLocalVariable = false;
AnalysisContext get analysisContext => analysisContext2;
/**
* Return a type provider that can be used to test the results of resolution.
*
* @return a type provider
* @throws AnalysisException if dart:core cannot be resolved
*/
TypeProvider get typeProvider => analysisContext2.typeProvider;
/**
* Return a type system that can be used to test the results of resolution.
*
* @return a type system
*/
TypeSystem get typeSystem => analysisContext2.typeSystem;
/**
* Add a source file with the given [filePath] in the root of the file system.
* The file path should be absolute. The file will have the given [contents]
* set in the content provider. Return the source representing the added file.
*/
Source addNamedSource(String filePath, String contents) {
Source source =
cacheSource(resourceProvider.convertPath(filePath), contents);
ChangeSet changeSet = new ChangeSet();
changeSet.addedSource(source);
analysisContext2.applyChanges(changeSet);
return source;
}
/**
* Add a source file named 'test.dart' in the root of the file system. The
* file will have the given [contents] set in the content provider. Return the
* source representing the added file.
*/
Source addSource(String contents) => addNamedSource("/test.dart", contents);
/**
* Assert that the number of errors reported against the given source matches the number of errors
* that are given and that they have the expected error codes. The order in which the errors were
* gathered is ignored.
*
* @param source the source against which the errors should have been reported
* @param expectedErrorCodes the error codes of the errors that should have been reported
* @throws AnalysisException if the reported errors could not be computed
* @throws AssertionFailedError if a different number of errors have been reported than were
* expected
*/
void assertErrors(Source source,
[List<ErrorCode> expectedErrorCodes = const <ErrorCode>[]]) {
GatheringErrorListener errorListener = new GatheringErrorListener();
for (AnalysisError error in analysisContext2.computeErrors(source)) {
expect(error.source, source);
ErrorCode errorCode = error.errorCode;
if (!enableUnusedElement &&
(errorCode == HintCode.UNUSED_ELEMENT ||
errorCode == HintCode.UNUSED_FIELD)) {
continue;
}
if (!enableUnusedLocalVariable &&
(errorCode == HintCode.UNUSED_CATCH_CLAUSE ||
errorCode == HintCode.UNUSED_CATCH_STACK ||
errorCode == HintCode.UNUSED_LOCAL_VARIABLE)) {
continue;
}
errorListener.onError(error);
}
errorListener.assertErrorsWithCodes(expectedErrorCodes);
}
/**
* Asserts that [code] verifies, but has errors with the given error codes.
*
* Like [assertErrors], but takes a string of source code.
*/
// TODO(rnystrom): Use this in more tests that have the same structure.
void assertErrorsInCode(String code, List<ErrorCode> errors) {
Source source = addSource(code);
computeLibrarySourceErrors(source);
assertErrors(source, errors);
verify([source]);
}
/**
* Asserts that [code] has errors with the given error codes.
*
* Like [assertErrors], but takes a string of source code.
*/
void assertErrorsInUnverifiedCode(String code, List<ErrorCode> errors) {
Source source = addSource(code);
computeLibrarySourceErrors(source);
assertErrors(source, errors);
}
/**
* Assert that no errors have been reported against the given source.
*
* @param source the source against which no errors should have been reported
* @throws AnalysisException if the reported errors could not be computed
* @throws AssertionFailedError if any errors have been reported
*/
void assertNoErrors(Source source) {
assertErrors(source);
}
/**
* Asserts that [code] has no errors or warnings.
*/
// TODO(rnystrom): Use this in more tests that have the same structure.
void assertNoErrorsInCode(String code) {
Source source = addSource(code);
computeLibrarySourceErrors(source);
assertNoErrors(source);
verify([source]);
}
/**
* @param code the code that assigns the value to the variable "v", no matter how. We check that
* "v" has expected static and propagated type.
*/
void assertPropagatedAssignedType(String code, DartType expectedStaticType,
DartType expectedPropagatedType) {
SimpleIdentifier identifier = findMarkedIdentifier(code, "v = ");
expect(identifier.staticType, same(expectedStaticType));
expect(identifier.propagatedType, same(expectedPropagatedType));
}
/**
* @param code the code that iterates using variable "v". We check that
* "v" has expected static and propagated type.
*/
void assertPropagatedIterationType(String code, DartType expectedStaticType,
DartType expectedPropagatedType) {
SimpleIdentifier identifier = findMarkedIdentifier(code, "v in ");
expect(identifier.staticType, same(expectedStaticType));
expect(identifier.propagatedType, same(expectedPropagatedType));
}
/**
* Check the static and propagated types of the expression marked with "; // marker" comment.
*
* @param code source code to analyze, with the expression to check marked with "// marker".
* @param expectedStaticType if non-null, check actual static type is equal to this.
* @param expectedPropagatedType if non-null, check actual static type is equal to this.
* @throws Exception
*/
void assertTypeOfMarkedExpression(String code, DartType expectedStaticType,
DartType expectedPropagatedType) {
SimpleIdentifier identifier = findMarkedIdentifier(code, "; // marker");
if (expectedStaticType != null) {
expect(identifier.staticType, expectedStaticType);
}
expect(identifier.propagatedType, expectedPropagatedType);
}
/**
* Cache the [contents] for the file at the given [filePath] but don't add the
* source to the analysis context. The file path must be absolute.
*/
Source cacheSource(String filePath, String contents) {
Source source = resourceProvider.getFile(filePath).createSource();
analysisContext2.setContents(source, contents);
return source;
}
/**
* Change the contents of the given [source] to the given [contents].
*/
void changeSource(Source source, String contents) {
analysisContext2.setContents(source, contents);
ChangeSet changeSet = new ChangeSet();
changeSet.changedSource(source);
analysisContext2.applyChanges(changeSet);
}
/**
* Computes errors for the given [librarySource].
* This assumes that the given [librarySource] and its parts have already
* been added to the content provider using the method [addNamedSource].
*/
void computeLibrarySourceErrors(Source librarySource) {
analysisContext.computeErrors(librarySource);
}
/**
* Create a library element that represents a library named `"test"` containing a single
* empty compilation unit.
*
* @return the library element that was created
*/
LibraryElementImpl createDefaultTestLibrary() =>
createTestLibrary(AnalysisContextFactory.contextWithCore(), "test");
/**
* Create a source object representing a file with the given [fileName] and
* give it an empty content. Return the source that was created.
*/
Source createNamedSource(String fileName) {
Source source = resourceProvider.getFile(fileName).createSource();
analysisContext2.setContents(source, '');
return source;
}
/**
* Create a library element that represents a library with the given name containing a single
* empty compilation unit.
*
* @param libraryName the name of the library to be created
* @return the library element that was created
*/
LibraryElementImpl createTestLibrary(
AnalysisContext context, String libraryName,
[List<String> typeNames]) {
String fileName = "/test/$libraryName.dart";
Source definingCompilationUnitSource = createNamedSource(fileName);
List<CompilationUnitElement> sourcedCompilationUnits;
if (typeNames == null) {
sourcedCompilationUnits = CompilationUnitElement.EMPTY_LIST;
} else {
int count = typeNames.length;
sourcedCompilationUnits = new List<CompilationUnitElement>(count);
for (int i = 0; i < count; i++) {
String typeName = typeNames[i];
ClassElementImpl type =
new ClassElementImpl.forNode(AstTestFactory.identifier3(typeName));
String fileName = "$typeName.dart";
CompilationUnitElementImpl compilationUnit =
new CompilationUnitElementImpl(fileName);
compilationUnit.source = createNamedSource(fileName);
compilationUnit.librarySource = definingCompilationUnitSource;
compilationUnit.types = <ClassElement>[type];
sourcedCompilationUnits[i] = compilationUnit;
}
}
CompilationUnitElementImpl compilationUnit =
new CompilationUnitElementImpl(fileName);
compilationUnit.librarySource =
compilationUnit.source = definingCompilationUnitSource;
LibraryElementImpl library = new LibraryElementImpl.forNode(
context, AstTestFactory.libraryIdentifier2([libraryName]));
library.definingCompilationUnit = compilationUnit;
library.parts = sourcedCompilationUnits;
return library;
}
/**
* Return the `SimpleIdentifier` marked by `marker`. The source code must have no
* errors and be verifiable.
*
* @param code source code to analyze.
* @param marker marker identifying sought after expression in source code.
* @return expression marked by the marker.
* @throws Exception
*/
SimpleIdentifier findMarkedIdentifier(String code, String marker) {
try {
Source source = addSource(code);
LibraryElement library = resolve2(source);
assertNoErrors(source);
verify([source]);
CompilationUnit unit = resolveCompilationUnit(source, library);
// Could generalize this further by making [SimpleIdentifier.class] a
// parameter.
return EngineTestCase.findNode(
unit, code, marker, (node) => node is SimpleIdentifier);
} catch (exception) {
// Is there a better exception to throw here? The point is that an
// assertion failure here should be a failure, in both "test_*" and
// "fail_*" tests. However, an assertion failure is success for the
// purpose of "fail_*" tests, so without catching them here "fail_*" tests
// can succeed by failing for the wrong reason.
throw new StateError("Unexpected assertion failure: $exception");
}
}
Expression findTopLevelConstantExpression(
CompilationUnit compilationUnit, String name) =>
findTopLevelDeclaration(compilationUnit, name).initializer;
VariableDeclaration findTopLevelDeclaration(
CompilationUnit compilationUnit, String name) {
for (CompilationUnitMember member in compilationUnit.declarations) {
if (member is TopLevelVariableDeclaration) {
for (VariableDeclaration variable in member.variables.variables) {
if (variable.name.name == name) {
return variable;
}
}
}
}
return null;
// Not found
}
/**
* Re-create the analysis context being used by the test case.
*/
void reset() {
analysisContext2 = AnalysisContextFactory.contextWithCore(
resourceProvider: resourceProvider);
}
/**
* Re-create the analysis context being used by the test case and set the
* [options] in the newly created context to the given [options].
*/
void resetWithOptions(AnalysisOptions options) {
analysisContext2 = AnalysisContextFactory.contextWithCoreAndOptions(options,
resourceProvider: resourceProvider);
}
/**
* Given a library and all of its parts, resolve the contents of the library and the contents of
* the parts. This assumes that the sources for the library and its parts have already been added
* to the content provider using the method [addNamedSource].
*
* @param librarySource the source for the compilation unit that defines the library
* @return the element representing the resolved library
* @throws AnalysisException if the analysis could not be performed
*/
LibraryElement resolve2(Source librarySource) =>
analysisContext2.computeLibraryElement(librarySource);
/**
* Return the resolved compilation unit corresponding to the given source in the given library.
*
* @param source the source of the compilation unit to be returned
* @param library the library in which the compilation unit is to be resolved
* @return the resolved compilation unit
* @throws Exception if the compilation unit could not be resolved
*/
CompilationUnit resolveCompilationUnit(
Source source, LibraryElement library) =>
analysisContext2.resolveCompilationUnit(source, library);
CompilationUnit resolveSource(String sourceText) =>
resolveSource2("/test.dart", sourceText);
CompilationUnit resolveSource2(String fileName, String sourceText) {
Source source = addNamedSource(fileName, sourceText);
LibraryElement library = analysisContext.computeLibraryElement(source);
return analysisContext.resolveCompilationUnit(source, library);
}
Source resolveSources(List<String> sourceTexts) {
for (int i = 0; i < sourceTexts.length; i++) {
CompilationUnit unit =
resolveSource2("/lib${i + 1}.dart", sourceTexts[i]);
// reference the source if this is the last source
if (i + 1 == sourceTexts.length) {
return unit.element.source;
}
}
return null;
}
void resolveWithAndWithoutExperimental(
List<String> strSources,
List<ErrorCode> codesWithoutExperimental,
List<ErrorCode> codesWithExperimental) {
// Setup analysis context as non-experimental
AnalysisOptionsImpl options = new AnalysisOptionsImpl();
// options.enableDeferredLoading = false;
resetWithOptions(options);
// Analysis and assertions
Source source = resolveSources(strSources);
assertErrors(source, codesWithoutExperimental);
verify([source]);
// Setup analysis context as experimental
reset();
// Analysis and assertions
source = resolveSources(strSources);
assertErrors(source, codesWithExperimental);
verify([source]);
}
void resolveWithErrors(List<String> strSources, List<ErrorCode> codes) {
// Analysis and assertions
Source source = resolveSources(strSources);
assertErrors(source, codes);
verify([source]);
}
@override
void setUp() {
ElementFactory.flushStaticState();
super.setUp();
reset();
}
@override
void tearDown() {
analysisContext2 = null;
super.tearDown();
}
/**
* Verify that all of the identifiers in the compilation units associated with
* the given [sources] have been resolved.
*/
void verify(List<Source> sources) {
ResolutionVerifier verifier = new ResolutionVerifier();
for (Source source in sources) {
List<Source> libraries = analysisContext2.getLibrariesContaining(source);
for (Source library in libraries) {
analysisContext2
.resolveCompilationUnit2(source, library)
.accept(verifier);
}
}
verifier.assertResolved();
}
}
/**
* Shared infrastructure for [StaticTypeAnalyzer2Test] and
* [StrongModeStaticTypeAnalyzer2Test].
*/
class StaticTypeAnalyzer2TestShared extends ResolverTestCase {
String testCode;
Source testSource;
CompilationUnit testUnit;
/**
* Looks up the identifier with [name] and validates that its type type
* stringifies to [type] and that its generics match the given stringified
* output.
*/
expectFunctionType(String name, String type,
{String elementTypeParams: '[]',
String typeParams: '[]',
String typeArgs: '[]',
String typeFormals: '[]'}) {
typeParameters(Element element) {
if (element is ExecutableElement) {
return element.typeParameters;
} else if (element is ParameterElement) {
return element.typeParameters;
}
fail('Wrong element type: ${element.runtimeType}');
}
SimpleIdentifier identifier = findIdentifier(name);
// Element is either ExecutableElement or ParameterElement.
Element element = identifier.staticElement;
FunctionTypeImpl functionType = identifier.staticType;
expect(functionType.toString(), type);
expect(typeParameters(element).toString(), elementTypeParams);
expect(functionType.typeParameters.toString(), typeParams);
expect(functionType.typeArguments.toString(), typeArgs);
expect(functionType.typeFormals.toString(), typeFormals);
}
/**
* Looks up the identifier with [name] and validates its static [type].
*
* If [type] is a string, validates that the identifier's static type
* stringifies to that text. Otherwise, [type] is used directly a [Matcher]
* to match the type.
*
* If [propagatedType] is given, also validate's the identifier's propagated
* type.
*/
void expectIdentifierType(String name, type, [propagatedType]) {
SimpleIdentifier identifier = findIdentifier(name);
_expectType(identifier.staticType, type);
if (propagatedType != null) {
_expectType(identifier.propagatedType, propagatedType);
}
}
/**
* Looks up the initializer for the declaration containing [identifier] and
* validates its static [type].
*
* If [type] is a string, validates that the identifier's static type
* stringifies to that text. Otherwise, [type] is used directly a [Matcher]
* to match the type.
*
* If [propagatedType] is given, also validate's the identifier's propagated
* type.
*/
void expectInitializerType(String name, type, [propagatedType]) {
SimpleIdentifier identifier = findIdentifier(name);
VariableDeclaration declaration =
identifier.getAncestor((node) => node is VariableDeclaration);
Expression initializer = declaration.initializer;
_expectType(initializer.staticType, type);
if (propagatedType != null) {
_expectType(initializer.propagatedType, propagatedType);
}
}
SimpleIdentifier findIdentifier(String search) {
SimpleIdentifier identifier = EngineTestCase.findNode(
testUnit, testCode, search, (node) => node is SimpleIdentifier);
return identifier;
}
void resolveTestUnit(String code) {
testCode = code;
testSource = addSource(testCode);
LibraryElement library = resolve2(testSource);
assertNoErrors(testSource);
verify([testSource]);
testUnit = resolveCompilationUnit(testSource, library);
}
/**
* Validates that [type] matches [expected].
*
* If [expected] is a string, validates that the type stringifies to that
* text. Otherwise, [expected] is used directly a [Matcher] to match the type.
*/
_expectType(DartType type, expected) {
if (expected is String) {
expect(type.toString(), expected);
} else {
expect(type, expected);
}
}
}