| // Copyright (c) 2018, 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/analysis/features.dart'; |
| import 'package:analyzer/dart/analysis/results.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/nullability_suffix.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/dart/element/type_provider.dart'; |
| import 'package:analyzer/error/error.dart'; |
| import 'package:analyzer/src/dart/ast/ast.dart'; |
| import 'package:analyzer/src/dart/ast/extensions.dart'; |
| import 'package:analyzer/src/dart/element/element.dart'; |
| import 'package:analyzer/src/dart/element/member.dart'; |
| import 'package:analyzer/src/dart/element/type.dart'; |
| import 'package:analyzer/src/dart/element/type_algebra.dart'; |
| import 'package:analyzer/src/dart/element/type_system.dart'; |
| import 'package:analyzer/src/test_utilities/find_element.dart'; |
| import 'package:analyzer/src/test_utilities/find_node.dart'; |
| import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart'; |
| import 'package:test/test.dart'; |
| |
| import '../../../generated/test_support.dart'; |
| |
| final isDynamicType = TypeMatcher<DynamicTypeImpl>(); |
| |
| final isNeverType = TypeMatcher<NeverTypeImpl>(); |
| |
| final isVoidType = TypeMatcher<VoidTypeImpl>(); |
| |
| /// Base for resolution tests. |
| mixin ResolutionTest implements ResourceProviderMixin { |
| late ResolvedUnitResult result; |
| late FindNode findNode; |
| late FindElement findElement; |
| |
| ClassElement get boolElement => typeProvider.boolElement; |
| |
| ClassElement get doubleElement => typeProvider.doubleType.element; |
| |
| InterfaceType get doubleType => typeProvider.doubleType; |
| |
| Element get dynamicElement => typeProvider.dynamicType.element!; |
| |
| FeatureSet get featureSet => result.libraryElement.featureSet; |
| |
| ClassElement get futureElement => typeProvider.futureElement; |
| |
| /// TODO(scheglov) https://github.com/dart-lang/sdk/issues/43608 |
| bool get hasAssignmentLeftResolution => false; |
| |
| ClassElement get intElement => typeProvider.intType.element; |
| |
| InterfaceType get intType => typeProvider.intType; |
| |
| bool get isLegacyLibrary { |
| return !result.libraryElement.isNonNullableByDefault; |
| } |
| |
| ClassElement get listElement => typeProvider.listElement; |
| |
| ClassElement get mapElement => typeProvider.mapElement; |
| |
| NeverElementImpl get neverElement => NeverElementImpl.instance; |
| |
| ClassElement get numElement => typeProvider.numType.element; |
| |
| ClassElement get objectElement => typeProvider.objectType.element; |
| |
| InterfaceType get objectType => typeProvider.objectType; |
| |
| ClassElement get stringElement => typeProvider.stringType.element; |
| |
| InterfaceType get stringType => typeProvider.stringType; |
| |
| String get testFilePath => '/test/lib/test.dart'; |
| |
| TypeProvider get typeProvider => result.typeProvider; |
| |
| TypeSystemImpl get typeSystem => result.typeSystem as TypeSystemImpl; |
| |
| /// Whether `DartType.toString()` with nullability should be asked. |
| bool get typeToStringWithNullability => false; |
| |
| VoidType get voidType => VoidTypeImpl.instance; |
| |
| void addTestFile(String content) { |
| newFile(testFilePath, content: content); |
| } |
| |
| void assertAssignment( |
| AssignmentExpression node, { |
| required Object? readElement, |
| required String? readType, |
| required Object? writeElement, |
| required String writeType, |
| required Object? operatorElement, |
| required String type, |
| }) { |
| assertCompoundAssignment( |
| node, |
| readElement: readElement, |
| readType: readType, |
| writeElement: writeElement, |
| writeType: writeType, |
| ); |
| assertElement(node.staticElement, operatorElement); |
| assertType(node, type); |
| } |
| |
| void assertBinaryExpression( |
| BinaryExpression node, { |
| required Object? element, |
| required String type, |
| }) { |
| assertElement(node.staticElement, element); |
| assertType(node, type); |
| } |
| |
| /// Assert that the given [identifier] is a reference to a class, in the |
| /// form that is not a separate expression, e.g. in a static method |
| /// invocation like `C.staticMethod()`, or a type annotation `C c = null`. |
| void assertClassRef(Expression? identifier, ClassElement expectedElement) { |
| identifier as SimpleIdentifier; |
| assertElement(identifier, expectedElement); |
| assertTypeNull(identifier); |
| } |
| |
| void assertCompoundAssignment( |
| CompoundAssignmentExpression node, { |
| required Object? readElement, |
| required String? readType, |
| required Object? writeElement, |
| required String? writeType, |
| }) { |
| assertElement(node.readElement, readElement); |
| if (readType == null) { |
| expect(node.readType, isNull); |
| } else { |
| assertType(node.readType, readType); |
| } |
| |
| assertElement(node.writeElement, writeElement); |
| if (writeType == null) { |
| expect(node.writeType, isNull); |
| } else { |
| assertType(node.writeType, writeType); |
| } |
| } |
| |
| void assertConstructorElement( |
| ConstructorElement? expected, ConstructorElement? actual) { |
| if (expected is ConstructorMember && actual is ConstructorMember) { |
| expect(expected.declaration, same(actual.declaration)); |
| // TODO(brianwilkerson) Compare the type arguments of the two members. |
| } else { |
| expect(expected, same(actual)); |
| } |
| } |
| |
| void assertConstructorReference( |
| ConstructorReference node, |
| Object? expectedConstructorElement, |
| ClassElement expectedClassElement, |
| String expectedType, { |
| PrefixElement? expectedPrefix, |
| Element? expectedTypeNameElement, |
| }) { |
| var actualConstructorElement = getNodeElement(node) as ConstructorElement?; |
| var actualConstructorName = node.constructorName.name; |
| if (actualConstructorName != null) { |
| assertConstructorElement( |
| actualConstructorName.staticElement as ConstructorElement?, |
| actualConstructorElement, |
| ); |
| } |
| |
| assertElement(node, expectedConstructorElement); |
| assertType(node, expectedType); |
| |
| var namedType = node.constructorName.type2; |
| expectedTypeNameElement ??= expectedClassElement; |
| assertNamedType(namedType, expectedTypeNameElement, null, |
| expectedPrefix: expectedPrefix); |
| } |
| |
| void assertConstructors(ClassElement class_, List<String> expected) { |
| expect( |
| class_.constructors.map((c) { |
| return c.getDisplayString(withNullability: false); |
| }).toList(), |
| unorderedEquals(expected), |
| ); |
| } |
| |
| void assertElement(Object? nodeOrElement, Object? elementOrMatcher) { |
| Element? element; |
| if (nodeOrElement is AstNode) { |
| element = getNodeElement(nodeOrElement); |
| } else { |
| element = nodeOrElement as Element?; |
| } |
| |
| expect(element, _elementMatcher(elementOrMatcher)); |
| } |
| |
| void assertElement2( |
| Object? nodeOrElement, { |
| required Element declaration, |
| bool isLegacy = false, |
| Map<String, String> substitution = const {}, |
| }) { |
| Element? element; |
| if (nodeOrElement is AstNode) { |
| element = getNodeElement(nodeOrElement); |
| } else { |
| element = nodeOrElement as Element?; |
| } |
| |
| var actualDeclaration = element?.declaration; |
| expect(actualDeclaration, same(declaration)); |
| |
| if (element is Member) { |
| expect(element.isLegacy, isLegacy); |
| assertSubstitution(element.substitution, substitution); |
| } else { |
| if (isLegacy || substitution.isNotEmpty) { |
| fail('Expected to be a Member: (${element.runtimeType}) $element'); |
| } |
| } |
| } |
| |
| void assertElementLibraryUri(Element? element, String expected) { |
| var uri = element!.library!.source.uri; |
| expect('$uri', expected); |
| } |
| |
| void assertElementName(Element element, String name, |
| {bool isSynthetic = false, int? offset}) { |
| expect(element.name, name); |
| expect(element.isSynthetic, isSynthetic); |
| if (offset != null) { |
| expect(element.nameOffset, offset); |
| } |
| } |
| |
| void assertElementNull(Object? nodeOrElement) { |
| Element? element; |
| if (nodeOrElement is AstNode) { |
| element = getNodeElement(nodeOrElement); |
| } else { |
| element = nodeOrElement as Element?; |
| } |
| |
| expect(element, isNull); |
| } |
| |
| void assertElementString(Element element, String expected) { |
| var str = element.getDisplayString( |
| withNullability: typeToStringWithNullability, |
| ); |
| expect(str, expected); |
| } |
| |
| void assertElementTypes(List<DartType>? types, List<String> expected, |
| {bool ordered = false}) { |
| if (types == null) { |
| fail('Expected types, actually null.'); |
| } |
| |
| var typeStrList = types.map(typeString).toList(); |
| if (ordered) { |
| expect(typeStrList, expected); |
| } else { |
| expect(typeStrList, unorderedEquals(expected)); |
| } |
| } |
| |
| void assertEnclosingElement(Element element, Element expectedEnclosing) { |
| expect(element.enclosingElement, expectedEnclosing); |
| } |
| |
| Future<void> assertErrorsInCode( |
| String code, List<ExpectedError> expectedErrors) async { |
| addTestFile(code); |
| await resolveTestFile(); |
| |
| assertErrorsInResolvedUnit(result, expectedErrors); |
| } |
| |
| Future<ResolvedUnitResult> assertErrorsInFile( |
| String path, |
| String content, |
| List<ExpectedError> expectedErrors, |
| ) async { |
| path = convertPath(path); |
| newFile(path, content: content); |
| |
| var result = await resolveFile(path); |
| assertErrorsInResolvedUnit(result, expectedErrors); |
| |
| return result; |
| } |
| |
| Future<void> assertErrorsInFile2( |
| String path, |
| List<ExpectedError> expectedErrors, |
| ) async { |
| path = convertPath(path); |
| |
| var result = await resolveFile(path); |
| assertErrorsInResolvedUnit(result, expectedErrors); |
| } |
| |
| void assertErrorsInList( |
| List<AnalysisError> errors, |
| List<ExpectedError> expectedErrors, |
| ) { |
| GatheringErrorListener errorListener = GatheringErrorListener(); |
| errorListener.addAll(errors); |
| errorListener.assertErrors(expectedErrors); |
| } |
| |
| void assertErrorsInResolvedUnit( |
| ResolvedUnitResult result, |
| List<ExpectedError> expectedErrors, |
| ) { |
| assertErrorsInList(result.errors, expectedErrors); |
| } |
| |
| void assertErrorsInResult(List<ExpectedError> expectedErrors) { |
| assertErrorsInResolvedUnit(result, expectedErrors); |
| } |
| |
| void assertExtensionOverride( |
| ExtensionOverride node, { |
| required Object element, |
| required String extendedType, |
| required List<String> typeArgumentTypes, |
| }) { |
| assertElement(node, element); |
| assertType(node.extendedType, extendedType); |
| assertElementTypes(node.typeArgumentTypes, typeArgumentTypes); |
| } |
| |
| void assertFunctionExpressionInvocation( |
| FunctionExpressionInvocation node, { |
| required ExecutableElement? element, |
| required List<String> typeArgumentTypes, |
| required String invokeType, |
| required String type, |
| }) { |
| assertElement(node, element); |
| assertTypeArgumentTypes(node, typeArgumentTypes); |
| assertInvokeType(node, invokeType); |
| assertType(node, type); |
| } |
| |
| void assertFunctionReference( |
| FunctionReference node, Element? expectedElement, String expectedType) { |
| assertElement(node, expectedElement); |
| assertType(node, expectedType); |
| } |
| |
| void assertHasTestErrors() { |
| expect(result.errors, isNotEmpty); |
| } |
| |
| void assertIdentifierTopGetRef(SimpleIdentifier ref, String name) { |
| var getter = findElement.topGet(name); |
| assertElement(ref, getter); |
| |
| var type = typeString(getter.returnType); |
| assertType(ref, type); |
| } |
| |
| void assertIdentifierTopSetRef(SimpleIdentifier ref, String name) { |
| var setter = findElement.topSet(name); |
| assertElement(ref, setter); |
| |
| var type = typeString(setter.parameters[0].type); |
| assertType(ref, type); |
| } |
| |
| void assertImplicitCallReference(ImplicitCallReference node, |
| Element? expectedElement, String expectedType) { |
| assertElement(node, expectedElement); |
| assertType(node, expectedType); |
| } |
| |
| /// In valid code [element] must be a [PrefixElement], but for invalid code |
| /// like `int.double v;` we want to resolve `int` somehow. Still not type. |
| void assertImportPrefix(Expression? identifier, Element? element) { |
| identifier as SimpleIdentifier; |
| assertElement(identifier, element); |
| assertTypeNull(identifier); |
| } |
| |
| void assertIndexExpression( |
| IndexExpression node, { |
| required Object? readElement, |
| required Object? writeElement, |
| required String? type, |
| }) { |
| var isRead = node.inGetterContext(); |
| var isWrite = node.inSetterContext(); |
| if (isRead && isWrite) { |
| assertElement(node.staticElement, writeElement); |
| } else if (isRead) { |
| assertElement(node.staticElement, readElement); |
| } else { |
| expect(isWrite, isTrue); |
| assertElement(node.staticElement, writeElement); |
| } |
| |
| if (isRead) { |
| assertType(node, type); |
| } else { |
| // TODO(scheglov) enforce this |
| // expect(type, isNull); |
| // assertTypeNull(node); |
| } |
| } |
| |
| /// TODO(srawlins): Refactor to accept an `Object? expectedConstructor` which |
| /// can accept `elementMatcher` for generics, and simplify, similar to |
| /// [assertConstructorReference]. |
| void assertInstanceCreation( |
| InstanceCreationExpression creation, |
| ClassElement expectedClassElement, |
| String expectedType, { |
| String? constructorName, |
| bool expectedConstructorMember = false, |
| Map<String, String>? expectedSubstitution, |
| PrefixElement? expectedPrefix, |
| Element? expectedTypeNameElement, |
| }) { |
| var expectedConstructorElement = |
| _getConstructorElement(expectedClassElement, constructorName); |
| |
| var actualConstructorElement = |
| getNodeElement(creation) as ConstructorElement?; |
| var constructorName2 = creation.constructorName.name; |
| if (constructorName2 != null) { |
| // TODO(brianwilkerson) This used to enforce that the two elements were |
| // the same object, but the changes to the AstRewriteVisitor broke that. |
| // We should explore re-establishing this restriction for performance. |
| assertConstructorElement( |
| constructorName2.staticElement as ConstructorElement?, |
| actualConstructorElement, |
| ); |
| } |
| |
| if (expectedConstructorMember) { |
| expect(actualConstructorElement, const TypeMatcher<Member>()); |
| assertMember( |
| creation, |
| expectedConstructorElement, |
| expectedSubstitution!, |
| ); |
| } else { |
| assertElement(creation, expectedConstructorElement); |
| } |
| |
| assertType(creation, expectedType); |
| |
| var namedType = creation.constructorName.type2; |
| expectedTypeNameElement ??= expectedClassElement; |
| assertNamedType(namedType, expectedTypeNameElement, expectedType, |
| expectedPrefix: expectedPrefix); |
| } |
| |
| /// Resolve the [code], and ensure that it can be resolved without a crash, |
| /// and is invalid, i.e. produces a diagnostic. |
| Future<void> assertInvalidTestCode(String code) async { |
| await resolveTestCode(code); |
| assertHasTestErrors(); |
| } |
| |
| void assertInvokeType(Expression node, String expected) { |
| DartType? actual; |
| if (node is BinaryExpression) { |
| actual = node.staticInvokeType; |
| } else if (node is InvocationExpression) { |
| actual = node.staticInvokeType; |
| } else { |
| fail('Unsupported node: (${node.runtimeType}) $node'); |
| } |
| expect(typeString(actual!), expected); |
| } |
| |
| void assertInvokeTypeDynamic(InvocationExpression node) { |
| var actual = node.staticInvokeType; |
| expect(actual, isDynamicType); |
| } |
| |
| void assertInvokeTypeNull(BinaryExpression node) { |
| var actual = node.staticInvokeType; |
| expect(actual, isNull); |
| } |
| |
| void assertMember( |
| Object? elementOrNode, |
| Element expectedBase, |
| Map<String, String> expectedSubstitution, |
| ) { |
| Member? actual; |
| if (elementOrNode is Member) { |
| actual = elementOrNode; |
| } else { |
| actual = getNodeElement(elementOrNode as AstNode) as Member; |
| } |
| |
| expect(actual.declaration, same(expectedBase)); |
| |
| assertSubstitution(actual.substitution, expectedSubstitution); |
| } |
| |
| void assertMethodInvocation( |
| MethodInvocation invocation, |
| Object? expectedElement, |
| String expectedInvokeType, { |
| String? expectedMethodNameType, |
| String? expectedNameType, |
| String? expectedType, |
| List<String> expectedTypeArguments = const <String>[], |
| }) { |
| var invocationImpl = invocation as MethodInvocationImpl; |
| |
| // TODO(scheglov) Check for Member. |
| var element = invocation.methodName.staticElement; |
| if (expectedElement is Element) { |
| expect(element?.declaration, same(expectedElement)); |
| } else { |
| expect(element, expectedElement); |
| } |
| |
| // TODO(scheglov) Should we enforce this? |
| // if (expectedNameType == null) { |
| // if (expectedElement is ExecutableElement) { |
| // expectedNameType = expectedElement.type.displayName; |
| // } else if (expectedElement is VariableElement) { |
| // expectedNameType = expectedElement.type.displayName; |
| // } |
| // } |
| // assertType(invocation.methodName, expectedNameType); |
| |
| assertTypeArgumentTypes(invocation, expectedTypeArguments); |
| |
| assertInvokeType(invocation, expectedInvokeType); |
| |
| expectedType ??= _extractReturnType(expectedInvokeType); |
| assertType(invocation, expectedType); |
| |
| expectedMethodNameType ??= expectedInvokeType; |
| assertType(invocationImpl.methodNameType, expectedMethodNameType); |
| } |
| |
| void assertMethodInvocation2( |
| MethodInvocation node, { |
| required Object? element, |
| required List<String> typeArgumentTypes, |
| required String invokeType, |
| required String type, |
| }) { |
| assertElement(node.methodName, element); |
| assertTypeArgumentTypes(node, typeArgumentTypes); |
| assertType(node.staticInvokeType, invokeType); |
| assertType(node.staticType, type); |
| } |
| |
| void assertNamedParameterRef(String search, String name) { |
| var ref = findNode.simple(search); |
| assertElement(ref, findElement.parameter(name)); |
| assertTypeNull(ref); |
| } |
| |
| void assertNamedType( |
| NamedType node, Element? expectedElement, String? expectedType, |
| {Element? expectedPrefix}) { |
| assertType(node, expectedType); |
| |
| if (expectedPrefix == null) { |
| var name = node.name as SimpleIdentifier; |
| assertElement(name, expectedElement); |
| // TODO(scheglov) Should this be null? |
| // assertType(name, expectedType); |
| } else { |
| var name = node.name as PrefixedIdentifier; |
| assertImportPrefix(name.prefix, expectedPrefix); |
| assertElement(name.identifier, expectedElement); |
| |
| // TODO(scheglov) This should be null, but it is not. |
| // ResolverVisitor sets the tpe for `Bar` in `new foo.Bar()`. This is |
| // probably wrong. It is fine for the TypeName `foo.Bar` to have a type, |
| // and for `foo.Bar()` to have a type. But not a name of a type? No. |
| // expect(name.identifier.staticType, isNull); |
| } |
| } |
| |
| void assertNamespaceDirectiveSelected( |
| NamespaceDirective directive, { |
| required String expectedRelativeUri, |
| required String expectedUri, |
| }) { |
| expect(directive.selectedUriContent, expectedRelativeUri); |
| expect('${directive.selectedSource!.uri}', expectedUri); |
| } |
| |
| Future<void> assertNoErrorsInCode(String code) async { |
| addTestFile(code); |
| await resolveTestFile(); |
| |
| assertErrorsInResolvedUnit(result, const []); |
| } |
| |
| void assertNoErrorsInResult() { |
| assertErrorsInResult(const []); |
| } |
| |
| void assertParameterElement( |
| Expression expression, |
| ParameterElement expected, |
| ) { |
| expect(expression.staticParameterElement, expected); |
| } |
| |
| void assertParameterElementType(FormalParameter node, String expected) { |
| var parameterElement = node.declaredElement!; |
| assertType(parameterElement.type, expected); |
| } |
| |
| void assertPostfixExpression( |
| PostfixExpression node, { |
| required Object? readElement, |
| required String? readType, |
| required Object? writeElement, |
| required String? writeType, |
| required Object? element, |
| required String type, |
| }) { |
| assertCompoundAssignment( |
| node, |
| readElement: readElement, |
| readType: readType, |
| writeElement: writeElement, |
| writeType: writeType, |
| ); |
| assertElement(node.staticElement, element); |
| assertType(node, type); |
| } |
| |
| void assertPrefixedIdentifier( |
| PrefixedIdentifier node, { |
| required Object? element, |
| required String type, |
| }) { |
| assertElement(node.staticElement, element); |
| assertType(node, type); |
| } |
| |
| void assertPrefixExpression( |
| PrefixExpression node, { |
| required Object? readElement, |
| required String? readType, |
| required Object? writeElement, |
| required String? writeType, |
| required Object? element, |
| required String type, |
| }) { |
| assertCompoundAssignment( |
| node, |
| readElement: readElement, |
| readType: readType, |
| writeElement: writeElement, |
| writeType: writeType, |
| ); |
| assertElement(node.staticElement, element); |
| assertType(node, type); |
| } |
| |
| void assertPropertyAccess( |
| PropertyAccess access, |
| Element expectedElement, |
| String expectedType, |
| ) { |
| assertElement(access.propertyName, expectedElement); |
| assertType(access, expectedType); |
| } |
| |
| void assertPropertyAccess2( |
| PropertyAccess node, { |
| required Object? element, |
| required String type, |
| }) { |
| assertElement(node.propertyName.staticElement, element); |
| assertType(node.staticType, type); |
| } |
| |
| void assertSimpleIdentifier( |
| Expression node, { |
| required Object? element, |
| required String? type, |
| }) { |
| if (node is! SimpleIdentifier) { |
| _failNotSimpleIdentifier(node); |
| } |
| |
| var isRead = node.inGetterContext(); |
| expect(isRead, isTrue); |
| |
| assertElement(node.staticElement, element); |
| assertType(node, type); |
| } |
| |
| /// TODO(scheglov) https://github.com/dart-lang/sdk/issues/43608 |
| void assertSimpleIdentifierAssignmentTarget(Expression node) { |
| if (node is! SimpleIdentifier) { |
| _failNotSimpleIdentifier(node); |
| } |
| |
| // TODO(scheglov) Enforce maybe? |
| // Currently VariableResolverVisitor sets it. |
| // expect(node.staticElement, isNull); |
| expect(node.staticType, isNull); |
| } |
| |
| void assertSubstitution( |
| MapSubstitution substitution, |
| Map<String, String> expected, |
| ) { |
| var actualMapString = Map.fromEntries( |
| substitution.map.entries.where((entry) { |
| return entry.key.enclosingElement is! ExecutableElement; |
| }).map((entry) { |
| return MapEntry( |
| entry.key.name, |
| typeString(entry.value), |
| ); |
| }), |
| ); |
| expect(actualMapString, expected); |
| } |
| |
| void assertSuperExpression(Expression? node) { |
| if (node is! SuperExpression) { |
| fail('Expected SuperExpression: (${node.runtimeType}) $node'); |
| } |
| |
| // TODO(scheglov) I think `super` does not have type itself. |
| // It is just a signal to look for implemented method in the supertype. |
| // With mixins there isn't a type anyway. |
| // assertTypeNull(superExpression); |
| } |
| |
| void assertTopGetRef(String search, String name) { |
| var ref = findNode.simple(search); |
| assertIdentifierTopGetRef(ref, name); |
| } |
| |
| void assertType(Object? typeOrNode, String? expected) { |
| DartType? actual; |
| if (typeOrNode is DartType) { |
| actual = typeOrNode; |
| } else if (typeOrNode is Expression) { |
| actual = typeOrNode.staticType; |
| } else if (typeOrNode is GenericFunctionType) { |
| actual = typeOrNode.type; |
| } else if (typeOrNode is NamedType) { |
| actual = typeOrNode.type; |
| } else { |
| fail('Unsupported node: (${typeOrNode.runtimeType}) $typeOrNode'); |
| } |
| |
| if (expected == null) { |
| expect(actual, isNull); |
| } else if (actual == null) { |
| fail('Null, expected: $expected'); |
| } else { |
| expect(typeString(actual), expected); |
| } |
| } |
| |
| /// We have a contract with the Angular team that FunctionType(s) from |
| /// typedefs carry the element of the typedef, and the type arguments. |
| void assertTypeAlias( |
| DartType type, { |
| required TypeAliasElement element, |
| required List<String> typeArguments, |
| }) { |
| assertElement2(type.alias?.element, declaration: element); |
| assertElementTypes(type.alias?.typeArguments, typeArguments); |
| } |
| |
| /// Assert that the given [identifier] is a reference to a type alias, in the |
| /// form that is not a separate expression, e.g. in a static method |
| /// invocation like `C.staticMethod()`, or a type annotation `C c = null`. |
| void assertTypeAliasRef( |
| SimpleIdentifier identifier, TypeAliasElement expected) { |
| assertElement(identifier, expected); |
| assertTypeNull(identifier); |
| } |
| |
| void assertTypeArgumentTypes( |
| InvocationExpression node, |
| List<String> expected, |
| ) { |
| var actual = node.typeArgumentTypes!.map((t) => typeString(t)).toList(); |
| expect(actual, expected); |
| } |
| |
| void assertTypeDynamic(Object? typeOrExpression) { |
| DartType? actual; |
| if (typeOrExpression is DartType?) { |
| actual = typeOrExpression; |
| var type = typeOrExpression; |
| expect(type, isDynamicType); |
| } else { |
| actual = (typeOrExpression as Expression).staticType; |
| } |
| expect(actual, isDynamicType); |
| } |
| |
| void assertTypeLegacy(Expression? expression) { |
| expression!; |
| NullabilitySuffix actual = expression.typeOrThrow.nullabilitySuffix; |
| expect(actual, NullabilitySuffix.star); |
| } |
| |
| void assertTypeLiteral( |
| TypeLiteral node, Element? expectedElement, String expectedType, |
| {Element? expectedPrefix}) { |
| assertType(node, 'Type'); |
| assertNamedType(node.type, expectedElement, expectedType, |
| expectedPrefix: expectedPrefix); |
| } |
| |
| void assertTypeNull(Expression node) { |
| expect(node.staticType, isNull); |
| } |
| |
| /// TODO(scheglov) Remove `?` from [declaration]. |
| Matcher elementMatcher( |
| Element? declaration, { |
| bool isLegacy = false, |
| Map<String, String> substitution = const {}, |
| }) { |
| return _ElementMatcher( |
| this, |
| declaration: declaration!, |
| isLegacy: isLegacy, |
| substitution: substitution, |
| ); |
| } |
| |
| ExpectedError error(ErrorCode code, int offset, int length, |
| {Pattern? correctionContains, |
| String? text, |
| List<Pattern> messageContains = const [], |
| List<ExpectedContextMessage> contextMessages = |
| const <ExpectedContextMessage>[]}) => |
| ExpectedError(code, offset, length, |
| correctionContains: correctionContains, |
| message: text, |
| messageContains: messageContains, |
| expectedContextMessages: contextMessages); |
| |
| List<ExpectedError> expectedErrorsByNullability({ |
| required List<ExpectedError> nullable, |
| required List<ExpectedError> legacy, |
| }) { |
| if (typeToStringWithNullability) { |
| return nullable; |
| } else { |
| return legacy; |
| } |
| } |
| |
| Element? getNodeElement(AstNode node) { |
| if (node is Annotation) { |
| return node.element; |
| } else if (node is AssignmentExpression) { |
| return node.staticElement; |
| } else if (node is BinaryExpression) { |
| return node.staticElement; |
| } else if (node is ConstructorReference) { |
| return node.constructorName.staticElement; |
| } else if (node is Declaration) { |
| return node.declaredElement; |
| } else if (node is ExtensionOverride) { |
| return node.staticElement; |
| } else if (node is FormalParameter) { |
| return node.declaredElement; |
| } else if (node is FunctionExpressionInvocation) { |
| return node.staticElement; |
| } else if (node is FunctionReference) { |
| var function = node.function.unParenthesized; |
| if (function is Identifier) { |
| return function.staticElement; |
| } else if (function is PropertyAccess) { |
| return function.propertyName.staticElement; |
| } else if (function is ConstructorReference) { |
| return function.constructorName.staticElement; |
| } else { |
| fail('Unsupported node: (${function.runtimeType}) $function'); |
| } |
| } else if (node is Identifier) { |
| return node.staticElement; |
| } else if (node is ImplicitCallReference) { |
| return node.staticElement; |
| } else if (node is IndexExpression) { |
| return node.staticElement; |
| } else if (node is InstanceCreationExpression) { |
| return node.constructorName.staticElement; |
| } else if (node is MethodInvocation) { |
| return node.methodName.staticElement; |
| } else if (node is PostfixExpression) { |
| return node.staticElement; |
| } else if (node is PrefixExpression) { |
| return node.staticElement; |
| } else if (node is PropertyAccess) { |
| return node.propertyName.staticElement; |
| } else if (node is NamedType) { |
| return node.name.staticElement; |
| } else { |
| fail('Unsupported node: (${node.runtimeType}) $node'); |
| } |
| } |
| |
| ExpectedContextMessage message(String filePath, int offset, int length) => |
| ExpectedContextMessage(convertPath(filePath), offset, length); |
| |
| Matcher multiplyDefinedElementMatcher(List<Element> elements) { |
| return _MultiplyDefinedElementMatcher(elements); |
| } |
| |
| Future<ResolvedUnitResult> resolveFile(String path); |
| |
| /// Resolve the file with the [path] into [result]. |
| Future<void> resolveFile2(String path) async { |
| path = convertPath(path); |
| |
| result = await resolveFile(path); |
| |
| findNode = FindNode(result.content, result.unit); |
| findElement = FindElement(result.unit); |
| } |
| |
| /// Create a new file with the [path] and [content], resolve it into [result]. |
| Future<void> resolveFileCode(String path, String content) { |
| newFile(path, content: content); |
| return resolveFile2(path); |
| } |
| |
| /// Put the [code] into the test file, and resolve it. |
| Future<void> resolveTestCode(String code) { |
| addTestFile(code); |
| return resolveTestFile(); |
| } |
| |
| Future<void> resolveTestFile() { |
| return resolveFile2(testFilePath); |
| } |
| |
| /// Choose the type display string, depending on whether the [result] is |
| /// non-nullable or legacy. |
| String typeStr(String nonNullable, String legacy) { |
| if (result.libraryElement.isNonNullableByDefault) { |
| return nonNullable; |
| } else { |
| return legacy; |
| } |
| } |
| |
| /// Return a textual representation of the [type] that is appropriate for |
| /// tests. |
| String typeString(DartType type) => |
| type.getDisplayString(withNullability: typeToStringWithNullability); |
| |
| String typeStringByNullability({ |
| required String nullable, |
| required String legacy, |
| }) { |
| if (typeToStringWithNullability) { |
| return nullable; |
| } else { |
| return legacy; |
| } |
| } |
| |
| Matcher _elementMatcher(Object? elementOrMatcher) { |
| if (elementOrMatcher is Element) { |
| return _ElementMatcher(this, declaration: elementOrMatcher); |
| } else { |
| return wrapMatcher(elementOrMatcher); |
| } |
| } |
| |
| Never _failNotSimpleIdentifier(AstNode node) { |
| fail('Expected SimpleIdentifier: (${node.runtimeType}) $node'); |
| } |
| |
| ConstructorElement _getConstructorElement( |
| ClassElement classElement, String? constructorName) { |
| var constructorElement = constructorName == null |
| ? classElement.unnamedConstructor |
| : classElement.getNamedConstructor(constructorName); |
| return constructorElement ?? |
| fail("No constructor '${constructorName ?? '<unnamed>'}' in class " |
| "'${classElement.name}'."); |
| } |
| |
| static String _extractReturnType(String invokeType) { |
| int functionIndex = invokeType.indexOf(' Function'); |
| expect(functionIndex, isNonNegative); |
| return invokeType.substring(0, functionIndex); |
| } |
| } |
| |
| class _ElementMatcher extends Matcher { |
| final ResolutionTest test; |
| final Element declaration; |
| final bool isLegacy; |
| final Map<String, String> substitution; |
| |
| _ElementMatcher( |
| this.test, { |
| required this.declaration, |
| this.isLegacy = false, |
| this.substitution = const {}, |
| }); |
| |
| @override |
| Description describe(Description description) { |
| return description |
| .add('declaration: $declaration\n') |
| .add('isLegacy: $isLegacy\n') |
| .add('substitution: $substitution\n'); |
| } |
| |
| @override |
| bool matches(element, Map matchState) { |
| if (element is Element) { |
| if (!identical(element.declaration, declaration)) { |
| return false; |
| } |
| |
| if (element is Member) { |
| if (element.isLegacy != isLegacy) { |
| return false; |
| } |
| |
| test.assertSubstitution(element.substitution, substitution); |
| return true; |
| } else { |
| return !isLegacy && substitution.isEmpty; |
| } |
| } |
| return false; |
| } |
| } |
| |
| class _MultiplyDefinedElementMatcher extends Matcher { |
| final Iterable<Element> elements; |
| |
| _MultiplyDefinedElementMatcher(this.elements); |
| |
| @override |
| Description describe(Description description) { |
| return description.add('elements: $elements\n'); |
| } |
| |
| @override |
| bool matches(element, Map matchState) { |
| if (element is MultiplyDefinedElementImpl) { |
| var actualSet = element.conflictingElements.toSet(); |
| actualSet.removeAll(elements); |
| return actualSet.isEmpty; |
| } |
| return false; |
| } |
| } |