| // Copyright (c) 2019, 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/element/element.dart'; |
| import 'package:analyzer/dart/element/nullability_suffix.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/src/dart/element/element.dart'; |
| import 'package:analyzer/src/dart/element/type.dart'; |
| import 'package:analyzer/src/generated/resolver.dart'; |
| import 'package:analyzer/src/generated/testing/element_factory.dart'; |
| import 'package:analyzer/src/generated/testing/test_type_provider.dart'; |
| import 'package:analyzer/src/generated/utilities_dart.dart'; |
| import 'package:nnbd_migration/instrumentation.dart'; |
| import 'package:nnbd_migration/src/already_migrated_code_decorator.dart'; |
| import 'package:nnbd_migration/src/decorated_type.dart'; |
| import 'package:nnbd_migration/src/nullability_node.dart'; |
| import 'package:test/test.dart'; |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| |
| import 'migration_visitor_test_base.dart'; |
| |
| main() { |
| defineReflectiveSuite(() { |
| defineReflectiveTests(_AlreadyMigratedCodeDecoratorTestNormal); |
| defineReflectiveTests(_AlreadyMigratedCodeDecoratorTestProvisional); |
| }); |
| } |
| |
| class _AlreadyMigratedCodeDecoratorTestBase extends Object with EdgeTester { |
| final TypeProvider typeProvider; |
| |
| final AlreadyMigratedCodeDecorator decorator; |
| |
| final NullabilityGraphForTesting graph; |
| |
| final NullabilitySuffix suffix; |
| |
| Element element = _MockElement(); |
| |
| _AlreadyMigratedCodeDecoratorTestBase(NullabilitySuffix nullabilitySuffix) |
| : this._(nullabilitySuffix, NullabilityGraphForTesting(), |
| TestTypeProvider(nullabilitySuffix: nullabilitySuffix)); |
| |
| _AlreadyMigratedCodeDecoratorTestBase._( |
| this.suffix, this.graph, this.typeProvider) |
| : decorator = AlreadyMigratedCodeDecorator(graph, typeProvider); |
| |
| NullabilityNode get always => graph.always; |
| |
| NullabilityNode get never => graph.never; |
| |
| void checkAlwaysNullable(NullabilityNode node) { |
| var edge = assertEdge(always, node, hard: false); |
| var origin = graph.getEdgeOrigin(edge); |
| expect(origin.kind, EdgeOriginKind.alwaysNullableType); |
| expect(origin.element, same(element)); |
| } |
| |
| void checkDynamic(DecoratedType decoratedType) { |
| expect(decoratedType.type, same(typeProvider.dynamicType)); |
| checkAlwaysNullable(decoratedType.node); |
| } |
| |
| void checkExplicitlyNonNullable(NullabilityNode node) { |
| var edge = assertEdge(node, never, hard: true); |
| var origin = graph.getEdgeOrigin(edge); |
| expect(origin.kind, EdgeOriginKind.alreadyMigratedType); |
| expect(origin.element, same(element)); |
| } |
| |
| void checkExplicitlyNullable(NullabilityNode node) { |
| var edge = assertEdge(always, node, hard: false); |
| var origin = graph.getEdgeOrigin(edge); |
| expect(origin.kind, EdgeOriginKind.alreadyMigratedType); |
| expect(origin.element, same(element)); |
| } |
| |
| void checkFutureOr( |
| DecoratedType decoratedType, |
| void Function(NullabilityNode) checkNullability, |
| void Function(DecoratedType) checkArgument) { |
| expect(decoratedType.type.element, typeProvider.futureOrElement); |
| checkNullability(decoratedType.node); |
| checkArgument(decoratedType.typeArguments[0]); |
| } |
| |
| void checkInt(DecoratedType decoratedType, |
| void Function(NullabilityNode) checkNullability) { |
| expect(decoratedType.type.element, typeProvider.intType.element); |
| checkNullability(decoratedType.node); |
| } |
| |
| void checkIterable( |
| DecoratedType decoratedType, |
| void Function(NullabilityNode) checkNullability, |
| void Function(DecoratedType) checkArgument) { |
| expect( |
| decoratedType.type.element, typeProvider.iterableDynamicType.element); |
| checkNullability(decoratedType.node); |
| checkArgument(decoratedType.typeArguments[0]); |
| } |
| |
| void checkNum(DecoratedType decoratedType, |
| void Function(NullabilityNode) checkNullability) { |
| expect(decoratedType.type.element, typeProvider.numType.element); |
| checkNullability(decoratedType.node); |
| } |
| |
| void checkObject(DecoratedType decoratedType, |
| void Function(NullabilityNode) checkNullability) { |
| expect(decoratedType.type.element, typeProvider.objectType.element); |
| checkNullability(decoratedType.node); |
| } |
| |
| void checkTypeParameter( |
| DecoratedType decoratedType, |
| void Function(NullabilityNode) checkNullability, |
| TypeParameterElement expectedElement) { |
| var type = decoratedType.type as TypeParameterTypeImpl; |
| expect(type.element, same(expectedElement)); |
| checkNullability(decoratedType.node); |
| } |
| |
| void checkVoid(DecoratedType decoratedType) { |
| expect(decoratedType.type, same(typeProvider.voidType)); |
| checkAlwaysNullable(decoratedType.node); |
| } |
| |
| DecoratedType decorate(DartType type) { |
| var decoratedType = decorator.decorate(type, element); |
| expect(decoratedType.type, same(type)); |
| return decoratedType; |
| } |
| |
| test_decorate_dynamic() { |
| checkDynamic(decorate(typeProvider.dynamicType)); |
| } |
| |
| test_decorate_functionType_generic_bounded() { |
| var typeFormal = TypeParameterElementImpl.synthetic('T') |
| ..bound = typeProvider.numType; |
| var decoratedType = decorate( |
| FunctionTypeImpl( |
| typeFormals: [typeFormal], |
| parameters: const [], |
| returnType: TypeParameterTypeImpl(typeFormal), |
| nullabilitySuffix: suffix, |
| ), |
| ); |
| expect(decoratedType.typeFormalBounds, hasLength(1)); |
| checkNum(decoratedType.typeFormalBounds[0], checkExplicitlyNonNullable); |
| checkTypeParameter( |
| decoratedType.returnType, checkExplicitlyNonNullable, typeFormal); |
| } |
| |
| test_decorate_functionType_generic_no_explicit_bound() { |
| var typeFormal = TypeParameterElementImpl.synthetic('T'); |
| var decoratedType = decorate( |
| FunctionTypeImpl( |
| typeFormals: [typeFormal], |
| parameters: const [], |
| returnType: TypeParameterTypeImpl(typeFormal), |
| nullabilitySuffix: suffix, |
| ), |
| ); |
| expect(decoratedType.typeFormalBounds, hasLength(1)); |
| checkObject(decoratedType.typeFormalBounds[0], checkExplicitlyNullable); |
| checkTypeParameter( |
| decoratedType.returnType, checkExplicitlyNonNullable, typeFormal); |
| } |
| |
| test_decorate_functionType_named_parameter() { |
| checkDynamic( |
| decorate( |
| FunctionTypeImpl( |
| typeFormals: const [], |
| parameters: [ |
| ParameterElementImpl.synthetic( |
| 'x', |
| typeProvider.dynamicType, |
| ParameterKind.NAMED, |
| ) |
| ], |
| returnType: typeProvider.voidType, |
| nullabilitySuffix: suffix, |
| ), |
| ).namedParameters['x'], |
| ); |
| } |
| |
| test_decorate_functionType_ordinary_parameter() { |
| checkDynamic( |
| decorate( |
| FunctionTypeImpl( |
| typeFormals: const [], |
| parameters: [ |
| ParameterElementImpl.synthetic( |
| 'x', |
| typeProvider.dynamicType, |
| ParameterKind.REQUIRED, |
| ) |
| ], |
| returnType: typeProvider.voidType, |
| nullabilitySuffix: suffix, |
| ), |
| ).positionalParameters[0], |
| ); |
| } |
| |
| test_decorate_functionType_positional_parameter() { |
| checkDynamic( |
| decorate( |
| FunctionTypeImpl( |
| typeFormals: const [], |
| parameters: [ |
| ParameterElementImpl.synthetic( |
| 'x', |
| typeProvider.dynamicType, |
| ParameterKind.POSITIONAL, |
| ) |
| ], |
| returnType: typeProvider.voidType, |
| nullabilitySuffix: suffix, |
| ), |
| ).positionalParameters[0], |
| ); |
| } |
| |
| test_decorate_functionType_question() { |
| checkExplicitlyNullable( |
| decorate( |
| FunctionTypeImpl( |
| typeFormals: const [], |
| parameters: const [], |
| returnType: typeProvider.voidType, |
| nullabilitySuffix: NullabilitySuffix.question, |
| ), |
| ).node, |
| ); |
| } |
| |
| test_decorate_functionType_returnType() { |
| checkDynamic( |
| decorate( |
| FunctionTypeImpl( |
| typeFormals: const [], |
| parameters: const [], |
| returnType: typeProvider.dynamicType, |
| nullabilitySuffix: suffix, |
| ), |
| ).returnType, |
| ); |
| } |
| |
| test_decorate_functionType_star() { |
| checkExplicitlyNonNullable( |
| decorate( |
| FunctionTypeImpl( |
| typeFormals: const [], |
| parameters: const [], |
| returnType: typeProvider.voidType, |
| nullabilitySuffix: suffix, |
| ), |
| ).node, |
| ); |
| } |
| |
| test_decorate_interfaceType_simple_question() { |
| checkInt( |
| decorate(InterfaceTypeImpl(typeProvider.intType.element, |
| nullabilitySuffix: NullabilitySuffix.question)), |
| checkExplicitlyNullable); |
| } |
| |
| test_decorate_interfaceType_simple_star() { |
| checkInt( |
| decorate(InterfaceTypeImpl(typeProvider.intType.element, |
| nullabilitySuffix: suffix)), |
| checkExplicitlyNonNullable); |
| } |
| |
| test_decorate_iterable_dynamic() { |
| var decorated = decorate(typeProvider.iterableDynamicType); |
| checkIterable(decorated, checkExplicitlyNonNullable, checkDynamic); |
| } |
| |
| test_decorate_typeParameterType_question() { |
| var element = TypeParameterElementImpl.synthetic('T'); |
| checkTypeParameter( |
| decorate(TypeParameterTypeImpl(element, |
| nullabilitySuffix: NullabilitySuffix.question)), |
| checkExplicitlyNullable, |
| element); |
| } |
| |
| test_decorate_typeParameterType_star() { |
| var element = TypeParameterElementImpl.synthetic('T'); |
| checkTypeParameter( |
| decorate(TypeParameterTypeImpl(element, nullabilitySuffix: suffix)), |
| checkExplicitlyNonNullable, |
| element); |
| } |
| |
| test_decorate_void() { |
| checkVoid(decorate(typeProvider.voidType)); |
| } |
| |
| test_getImmediateSupertypes_future() { |
| var class_ = element = typeProvider.futureElement; |
| var decoratedSupertypes = decorator.getImmediateSupertypes(class_).toList(); |
| var typeParam = class_.typeParameters[0]; |
| expect(decoratedSupertypes, hasLength(2)); |
| checkObject(decoratedSupertypes[0], checkExplicitlyNonNullable); |
| // Since Future<T> is a subtype of FutureOr<T>, we consider FutureOr<T> to |
| // be an immediate supertype, even though the class declaration for Future |
| // doesn't mention FutureOr. |
| checkFutureOr(decoratedSupertypes[1], checkExplicitlyNonNullable, |
| (t) => checkTypeParameter(t, checkExplicitlyNonNullable, typeParam)); |
| } |
| |
| test_getImmediateSupertypes_generic() { |
| var t = ElementFactory.typeParameterElement('T'); |
| var class_ = element = ElementFactory.classElement3( |
| name: 'C', |
| typeParameters: [t], |
| supertype: typeProvider.iterableType2( |
| t.instantiate(nullabilitySuffix: suffix), |
| ), |
| ); |
| var decoratedSupertypes = decorator.getImmediateSupertypes(class_).toList(); |
| expect(decoratedSupertypes, hasLength(1)); |
| checkIterable(decoratedSupertypes[0], checkExplicitlyNonNullable, |
| (type) => checkTypeParameter(type, checkExplicitlyNonNullable, t)); |
| } |
| |
| test_getImmediateSupertypes_interface() { |
| var class_ = |
| element = ElementFactory.classElement('C', typeProvider.objectType); |
| class_.interfaces = [typeProvider.numType]; |
| var decoratedSupertypes = decorator.getImmediateSupertypes(class_).toList(); |
| expect(decoratedSupertypes, hasLength(2)); |
| checkObject(decoratedSupertypes[0], checkExplicitlyNonNullable); |
| checkNum(decoratedSupertypes[1], checkExplicitlyNonNullable); |
| } |
| |
| test_getImmediateSupertypes_mixin() { |
| var class_ = |
| element = ElementFactory.classElement('C', typeProvider.objectType); |
| class_.mixins = [typeProvider.numType]; |
| var decoratedSupertypes = decorator.getImmediateSupertypes(class_).toList(); |
| expect(decoratedSupertypes, hasLength(2)); |
| checkObject(decoratedSupertypes[0], checkExplicitlyNonNullable); |
| checkNum(decoratedSupertypes[1], checkExplicitlyNonNullable); |
| } |
| |
| test_getImmediateSupertypes_superclassConstraint() { |
| var class_ = element = ElementFactory.mixinElement( |
| name: 'C', constraints: [typeProvider.numType]); |
| var decoratedSupertypes = decorator.getImmediateSupertypes(class_).toList(); |
| expect(decoratedSupertypes, hasLength(1)); |
| checkNum(decoratedSupertypes[0], checkExplicitlyNonNullable); |
| } |
| |
| test_getImmediateSupertypes_supertype() { |
| var class_ = |
| element = ElementFactory.classElement('C', typeProvider.objectType); |
| var decoratedSupertypes = decorator.getImmediateSupertypes(class_).toList(); |
| expect(decoratedSupertypes, hasLength(1)); |
| checkObject(decoratedSupertypes[0], checkExplicitlyNonNullable); |
| } |
| } |
| |
| /// Specialization of [_AlreadyMigratedCodeDecoratorTestBase] for testing the |
| /// situation where the already migrated code does not contain star types. In |
| /// the final product, by definition all already-migrated code will be free of |
| /// star types. However, since we do not yet migrate using a fully NNBD-aware |
| /// SDK, we need to handle both star and non-star variants on a short term |
| /// basis. |
| @reflectiveTest |
| class _AlreadyMigratedCodeDecoratorTestNormal |
| extends _AlreadyMigratedCodeDecoratorTestBase { |
| _AlreadyMigratedCodeDecoratorTestNormal() : super(NullabilitySuffix.none); |
| } |
| |
| /// Specialization of [_AlreadyMigratedCodeDecoratorTestBase] for testing the |
| /// situation where the already migrated code contains star types. In the final |
| /// product, this will never happen. However, since we do not yet migrate using |
| /// a fully NNBD-aware SDK, we need to handle both star and non-star variants on |
| /// a short term basis. |
| @reflectiveTest |
| class _AlreadyMigratedCodeDecoratorTestProvisional |
| extends _AlreadyMigratedCodeDecoratorTestBase { |
| _AlreadyMigratedCodeDecoratorTestProvisional() |
| : super(NullabilitySuffix.star); |
| } |
| |
| class _MockElement implements Element { |
| noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); |
| } |