// 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/ast/ast.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/dart/element/type_provider.dart'; import 'package:analyzer/src/generated/utilities_dart.dart'; import 'package:nnbd_migration/nnbd_migration.dart'; import 'package:nnbd_migration/src/decorated_type.dart'; import 'package:nnbd_migration/src/edit_plan.dart'; import 'package:nnbd_migration/src/fix_aggregator.dart'; import 'package:nnbd_migration/src/nullability_node.dart'; import 'package:nnbd_migration/src/nullability_node_target.dart'; import 'package:test/test.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; import 'abstract_single_unit.dart'; main() { defineReflectiveSuite(() { defineReflectiveTests(FixAggregatorTest); }); } @reflectiveTest class FixAggregatorTest extends FixAggregatorTestBase { TypeProviderImpl get nnbdTypeProvider => (testAnalysisResult.typeProvider as TypeProviderImpl) .asNonNullableByDefault; Future test_addImport_after_library() async { await analyze(''' library foo; main() {} '''); var previewInfo = run({ findNode.unit: NodeChangeForCompilationUnit() ..addImport('package:collection/collection.dart', 'IterableExtension') }); expect(previewInfo.applyTo(code), ''' library foo; import 'package:collection/collection.dart' show IterableExtension; main() {} '''); } Future test_addImport_after_library_before_other() async { addPackageFile('fixnum', 'fixnum.dart', ''); await analyze(''' library foo; import 'package:fixnum/fixnum.dart'; main() {} '''); var previewInfo = run({ findNode.unit: NodeChangeForCompilationUnit() ..addImport('package:collection/collection.dart', 'IterableExtension') }); expect(previewInfo.applyTo(code), ''' library foo; import 'package:collection/collection.dart' show IterableExtension; import 'package:fixnum/fixnum.dart'; main() {} '''); } Future test_addImport_atEnd_multiple() async { addPackageFile('args', 'args.dart', ''); await analyze(''' import 'package:args/args.dart'; main() {} '''); var previewInfo = run({ findNode.unit: NodeChangeForCompilationUnit() ..addImport('package:fixnum/fixnum.dart', 'Int32') ..addImport('package:collection/collection.dart', 'IterableExtension') }); expect(previewInfo.applyTo(code), ''' import 'package:args/args.dart'; import 'package:collection/collection.dart' show IterableExtension; import 'package:fixnum/fixnum.dart' show Int32; main() {} '''); } Future test_addImport_atStart_multiple() async { addPackageFile('fixnum', 'fixnum.dart', ''); await analyze(''' import 'package:fixnum/fixnum.dart'; main() {} '''); var previewInfo = run({ findNode.unit: NodeChangeForCompilationUnit() ..addImport('package:collection/collection.dart', 'IterableExtension') ..addImport('package:args/args.dart', 'ArgParser') }); expect(previewInfo.applyTo(code), ''' import 'package:args/args.dart' show ArgParser; import 'package:collection/collection.dart' show IterableExtension; import 'package:fixnum/fixnum.dart'; main() {} '''); } Future test_addImport_before_export() async { await analyze(''' export 'dart:async'; main() {} '''); var previewInfo = run({ findNode.unit: NodeChangeForCompilationUnit() ..addImport('package:collection/collection.dart', 'IterableExtension') }); expect(previewInfo.applyTo(code), ''' import 'package:collection/collection.dart' show IterableExtension; export 'dart:async'; main() {} '''); } Future test_addImport_no_previous_imports_multiple() async { await analyze(''' main() {} '''); var previewInfo = run({ findNode.unit: NodeChangeForCompilationUnit() ..addImport('dart:async', 'Future') ..addImport('dart:math', 'sin') }); expect(previewInfo.applyTo(code), ''' import 'dart:async' show Future; import 'dart:math' show sin; main() {} '''); } Future test_addImport_recursive() async { addPackageFile('args', 'args.dart', ''); addPackageFile('fixnum', 'fixnum.dart', 'class Int32 {}'); await analyze(''' import 'package:args/args.dart'; import 'package:fixnum/fixnum.dart' show Int32; main() => null; '''); var previewInfo = run({ findNode.unit: NodeChangeForCompilationUnit() ..addImport('package:collection/collection.dart', 'IterableExtension'), findNode.import('package:fixnum').combinators[0]: NodeChangeForShowCombinator()..addName('Int64'), findNode.expression('null'): NodeChangeForExpression() ..addExpressionChange(NullCheckChange(MockDartType()), null) }); expect(previewInfo.applyTo(code), ''' import 'package:args/args.dart'; import 'package:collection/collection.dart' show IterableExtension; import 'package:fixnum/fixnum.dart' show Int32, Int64; main() => null!; '''); } Future test_addImport_sort_shown_names() async { await analyze(''' main() {} '''); var previewInfo = run({ findNode.unit: NodeChangeForCompilationUnit() ..addImport('dart:async', 'Stream') ..addImport('dart:async', 'Future') }); expect(previewInfo.applyTo(code), ''' import 'dart:async' show Future, Stream; main() {} '''); } Future test_addImport_sorted() async { addPackageFile('args', 'args.dart', ''); addPackageFile('fixnum', 'fixnum.dart', ''); await analyze(''' import 'package:args/args.dart'; import 'package:fixnum/fixnum.dart'; main() {} '''); var previewInfo = run({ findNode.unit: NodeChangeForCompilationUnit() ..addImport('package:collection/collection.dart', 'IterableExtension') }); expect(previewInfo.applyTo(code), ''' import 'package:args/args.dart'; import 'package:collection/collection.dart' show IterableExtension; import 'package:fixnum/fixnum.dart'; main() {} '''); } Future test_addImport_sorted_multiple() async { addPackageFile('collection', 'collection.dart', ''); await analyze(''' import 'package:collection/collection.dart'; main() {} '''); var previewInfo = run({ findNode.unit: NodeChangeForCompilationUnit() ..addImport('package:fixnum/fixnum.dart', 'Int32') ..addImport('package:args/args.dart', 'ArgParser') }); expect(previewInfo.applyTo(code), ''' import 'package:args/args.dart' show ArgParser; import 'package:collection/collection.dart'; import 'package:fixnum/fixnum.dart' show Int32; main() {} '''); } Future test_addRequired() async { await analyze('f({int x}) => 0;'); var previewInfo = run({ findNode.defaultParameter('int x'): NodeChangeForDefaultFormalParameter() ..addRequiredKeyword = true }); expect(previewInfo.applyTo(code), 'f({required int x}) => 0;'); } Future test_addRequired_afterMetadata() async { await analyze('f({@deprecated int x}) => 0;'); var previewInfo = run({ findNode.defaultParameter('int x'): NodeChangeForDefaultFormalParameter() ..addRequiredKeyword = true }); expect(previewInfo.applyTo(code), 'f({@deprecated required int x}) => 0;'); } Future test_addRequired_afterMetadata_andRequiredAnnotation() async { addMetaPackage(); var content = ''' import 'package:meta/meta.dart'; f({@required @deprecated int x}) {} '''; await analyze(content); var annotation = findNode.annotation('required'); var previewInfo = run({ findNode.defaultParameter('int x'): NodeChangeForDefaultFormalParameter() ..addRequiredKeyword = true ..annotationToRemove = annotation }); expect(previewInfo.applyTo(code), ''' import 'package:meta/meta.dart'; f({@deprecated required int x}) {} '''); expect(previewInfo.values, hasLength(2)); expect(previewInfo[content.indexOf('int ')], hasLength(1)); expect(previewInfo[content.indexOf('int ')].single.isInsertion, true); expect(previewInfo[content.indexOf('@required')], isNotNull); expect(previewInfo[content.indexOf('@required')].single.isDeletion, true); } Future test_addRequired_afterMetadata_beforeFinal() async { await analyze('f({@deprecated final int x}) => 0;'); var previewInfo = run({ findNode.defaultParameter('int x'): NodeChangeForDefaultFormalParameter() ..addRequiredKeyword = true }); expect(previewInfo.applyTo(code), 'f({@deprecated required final int x}) => 0;'); } Future test_addRequired_afterMetadata_beforeFunctionTyped() async { await analyze('f({@deprecated int x()}) => 0;'); var previewInfo = run({ findNode.defaultParameter('int x'): NodeChangeForDefaultFormalParameter() ..addRequiredKeyword = true }); expect( previewInfo.applyTo(code), 'f({@deprecated required int x()}) => 0;'); } Future test_addShownName_atEnd_multiple() async { await analyze("import 'dart:math' show cos;"); var previewInfo = run({ findNode.import('dart:math').combinators[0]: NodeChangeForShowCombinator() ..addName('tan') ..addName('sin') }); expect(previewInfo.applyTo(code), "import 'dart:math' show cos, sin, tan;"); } Future test_addShownName_atStart_multiple() async { await analyze("import 'dart:math' show tan;"); var previewInfo = run({ findNode.import('dart:math').combinators[0]: NodeChangeForShowCombinator() ..addName('sin') ..addName('cos') }); expect(previewInfo.applyTo(code), "import 'dart:math' show cos, sin, tan;"); } Future test_addShownName_sorted() async { await analyze("import 'dart:math' show cos, tan;"); var previewInfo = run({ findNode.import('dart:math').combinators[0]: NodeChangeForShowCombinator() ..addName('sin') }); expect(previewInfo.applyTo(code), "import 'dart:math' show cos, sin, tan;"); } Future test_addShownName_sorted_multiple() async { await analyze("import 'dart:math' show sin;"); var previewInfo = run({ findNode.import('dart:math').combinators[0]: NodeChangeForShowCombinator() ..addName('tan') ..addName('cos') }); expect(previewInfo.applyTo(code), "import 'dart:math' show cos, sin, tan;"); } Future test_adjacentFixes() async { await analyze('f(a, b) => a + b;'); var aRef = findNode.simple('a +'); var bRef = findNode.simple('b;'); var previewInfo = run({ aRef: NodeChangeForExpression() ..addExpressionChange(NullCheckChange(MockDartType()), _MockInfo()), bRef: NodeChangeForExpression() ..addExpressionChange(NullCheckChange(MockDartType()), _MockInfo()), findNode.binary('a + b'): NodeChangeForExpression() ..addExpressionChange(NullCheckChange(MockDartType()), _MockInfo()) }); expect(previewInfo.applyTo(code), 'f(a, b) => (a! + b!)!;'); } Future test_argument_list_drop_all_arguments() async { var content = ''' f([int x, int y]) => null; g(int x, int y) => f(x, y); '''; await analyze(content); var previewInfo = run({ findNode.methodInvocation('f(x').argumentList: NodeChangeForArgumentList() ..dropArgument(findNode.simple('y);'), null) ..dropArgument(findNode.simple('x, y'), null) }); expect(previewInfo.applyTo(code), ''' f([int x, int y]) => null; g(int x, int y) => f(); '''); } Future test_argument_list_drop_one_argument() async { var content = ''' f([int x, int y]) => null; g(int x, int y) => f(x, y); '''; await analyze(content); var previewInfo = run({ findNode.methodInvocation('f(x').argumentList: NodeChangeForArgumentList() ..dropArgument(findNode.simple('y);'), null) }); expect(previewInfo.applyTo(code), ''' f([int x, int y]) => null; g(int x, int y) => f(x); '''); } Future test_argument_list_recursive_changes() async { var content = ''' f([int x, int y]) => null; g(int x, int y) => f(x, y); '''; await analyze(content); var previewInfo = run({ findNode.methodInvocation('f(x').argumentList: NodeChangeForArgumentList() ..dropArgument(findNode.simple('y);'), null), findNode.simple('x, y'): NodeChangeForExpression() ..addExpressionChange(NullCheckChange(MockDartType()), null) }); expect(previewInfo.applyTo(code), ''' f([int x, int y]) => null; g(int x, int y) => f(x!); '''); } Future test_assignment_add_null_check() async { var content = 'f(int x, int y) => x += y;'; await analyze(content); var previewInfo = run({ findNode.assignment('+='): NodeChangeForAssignment() ..addExpressionChange(NullCheckChange(MockDartType()), null) }); expect(previewInfo.applyTo(code), 'f(int x, int y) => (x += y)!;'); } Future test_assignment_change_lhs() async { var content = 'f(List x, int y) => x[0] += y;'; await analyze(content); var previewInfo = run({ findNode.assignment('+='): NodeChangeForAssignment(), findNode.index('[0]').target: NodeChangeForExpression() ..addExpressionChange(NullCheckChange(MockDartType()), null) }); expect(previewInfo.applyTo(code), 'f(List x, int y) => x![0] += y;'); } Future test_assignment_change_rhs() async { var content = 'f(int x, int y) => x += y;'; await analyze(content); var assignment = findNode.assignment('+='); var previewInfo = run({ assignment: NodeChangeForAssignment(), assignment.rightHandSide: NodeChangeForExpression() ..addExpressionChange(NullCheckChange(MockDartType()), null) }); expect(previewInfo.applyTo(code), 'f(int x, int y) => x += y!;'); } Future test_assignment_compound_with_bad_combined_type() async { var content = 'f(int x, int y) => x += y;'; await analyze(content); var previewInfo = run({ findNode.assignment('+='): NodeChangeForAssignment() ..hasBadCombinedType = true }); expect(previewInfo.applyTo(code), content); expect(previewInfo, hasLength(1)); var edit = previewInfo[content.indexOf('+=')].single; expect(edit.info.description, NullabilityFixDescription.compoundAssignmentHasBadCombinedType); expect(edit.isInformative, isTrue); expect(edit.length, '+='.length); } Future test_assignment_compound_with_nullable_source() async { var content = 'f(int x, int y) => x += y;'; await analyze(content); var previewInfo = run({ findNode.assignment('+='): NodeChangeForAssignment() ..hasNullableSource = true }); expect(previewInfo.applyTo(code), content); expect(previewInfo, hasLength(1)); var edit = previewInfo[content.indexOf('+=')].single; expect(edit.info.description, NullabilityFixDescription.compoundAssignmentHasNullableSource); expect(edit.isInformative, isTrue); expect(edit.length, '+='.length); } Future test_assignment_introduce_as() async { var content = 'f(int x, int y) => x += y;'; await analyze(content); var previewInfo = run({ findNode.assignment('+='): NodeChangeForAssignment() ..addExpressionChange( IntroduceAsChange(nnbdTypeProvider.intType, isDowncast: false), null) }); expect(previewInfo.applyTo(code), 'f(int x, int y) => (x += y) as int;'); } Future test_assignment_weak_null_aware() async { var content = 'f(int x, int y) => x ??= y;'; await analyze(content); var previewInfo = run({ findNode.assignment('??='): NodeChangeForAssignment() ..isWeakNullAware = true }, warnOnWeakCode: true); expect(previewInfo.applyTo(code), content); expect(previewInfo, hasLength(1)); var edit = previewInfo[content.indexOf('??=')].single; expect(edit.info.description, NullabilityFixDescription.nullAwareAssignmentUnnecessaryInStrongMode); expect(edit.isInformative, isTrue); expect(edit.length, '??='.length); } Future test_assignment_weak_null_aware_remove() async { var content = 'f(int x, int y) => x ??= y;'; await analyze(content); var previewInfo = run({ findNode.assignment('??='): NodeChangeForAssignment() ..isWeakNullAware = true }, warnOnWeakCode: false); expect(previewInfo.applyTo(code), 'f(int x, int y) => x;'); } Future test_eliminateDeadIf_changesInKeptCode() async { await analyze(''' f(int i, int/*?*/ j) { if (i != null) j.isEven; } '''); var previewInfo = run({ findNode.statement('if'): NodeChangeForIfStatement() ..conditionValue = true, findNode.simple('j.isEven'): NodeChangeForExpression() ..addExpressionChange(NullCheckChange(MockDartType()), _MockInfo()) }); expect(previewInfo.applyTo(code), ''' f(int i, int/*?*/ j) { j!.isEven; } '''); } Future test_eliminateDeadIf_changesInKeptCode_expandBlock() async { await analyze(''' f(int i, int/*?*/ j) { if (i != null) { j.isEven; } } '''); var previewInfo = run({ findNode.statement('if'): NodeChangeForIfStatement() ..conditionValue = true, findNode.simple('j.isEven'): NodeChangeForExpression() ..addExpressionChange(NullCheckChange(MockDartType()), _MockInfo()) }); expect(previewInfo.applyTo(code), ''' f(int i, int/*?*/ j) { j!.isEven; } '''); } Future test_eliminateDeadIf_element_delete_drop_completely() async { await analyze(''' List f(int i) { return [if (i == null) null]; } '''); var previewInfo = run({ findNode.ifElement('=='): NodeChangeForIfElement()..conditionValue = false }); expect(previewInfo.applyTo(code), ''' List f(int i) { return []; } '''); } Future test_eliminateDeadIf_element_delete_drop_completely_not_in_sequence() async { await analyze(''' List f(int i) { return [for (var x in [1, 2, 3]) if (i == null) null]; } '''); var previewInfo = run({ findNode.ifElement('=='): NodeChangeForIfElement()..conditionValue = false }); // This is a little kludgy; we could drop the `for` loop, but it's difficult // to do so, and this is a rare enough corner case that it doesn't seem // worth it. Replacing the `if` with `...{}` has the right effect, since // it expands to nothing. expect(previewInfo.applyTo(code), ''' List f(int i) { return [for (var x in [1, 2, 3]) ...{}]; } '''); } Future test_eliminateDeadIf_element_delete_keep_else() async { await analyze(''' List f(int i) { return [if (i == null) null else i + 1]; } '''); var previewInfo = run({ findNode.ifElement('=='): NodeChangeForIfElement()..conditionValue = false }); expect(previewInfo.applyTo(code), ''' List f(int i) { return [i + 1]; } '''); } Future test_eliminateDeadIf_element_delete_keep_then() async { await analyze(''' List f(int i) { return [if (i == null) null else i + 1]; } '''); var previewInfo = run({ findNode.ifElement('=='): NodeChangeForIfElement()..conditionValue = true }); expect(previewInfo.applyTo(code), ''' List f(int i) { return [null]; } '''); } Future test_eliminateDeadIf_expression_delete_keep_else() async { await analyze(''' int f(int i) { return i == null ? null : i + 1; } '''); var previewInfo = run({ findNode.conditionalExpression('=='): NodeChangeForConditionalExpression() ..conditionValue = false }); expect(previewInfo.applyTo(code), ''' int f(int i) { return i + 1; } '''); } Future test_eliminateDeadIf_expression_delete_keep_then() async { await analyze(''' int f(int i) { return i == null ? null : i + 1; } '''); var previewInfo = run({ findNode.conditionalExpression('=='): NodeChangeForConditionalExpression() ..conditionValue = true }); expect(previewInfo.applyTo(code), ''' int f(int i) { return null; } '''); } Future test_eliminateDeadIf_statement_comment_keep_else() async { await analyze(''' int f(int i) { if (i == null) { return null; } else { return i + 1; } } '''); var previewInfo = run({ findNode.statement('if'): NodeChangeForIfStatement() ..conditionValue = false }, removeViaComments: true); expect(previewInfo.applyTo(code), ''' int f(int i) { /* if (i == null) { return null; } else { */ return i + 1; /* } */ } '''); } Future test_eliminateDeadIf_statement_comment_keep_then() async { await analyze(''' int f(int i) { if (i == null) { return null; } else { return i + 1; } } '''); var previewInfo = run({ findNode.statement('if'): NodeChangeForIfStatement() ..conditionValue = true }, removeViaComments: true); expect(previewInfo.applyTo(code), ''' int f(int i) { /* if (i == null) { */ return null; /* } else { return i + 1; } */ } '''); } Future test_eliminateDeadIf_statement_delete_drop_completely_false() async { await analyze(''' void f(int i) { if (i == null) { print('null'); } } '''); var previewInfo = run({ findNode.statement('if'): NodeChangeForIfStatement() ..conditionValue = false }); expect(previewInfo.applyTo(code), ''' void f(int i) {} '''); } Future test_eliminateDeadIf_statement_delete_drop_completely_not_in_block() async { await analyze(''' void f(int i) { while (true) if (i == null) { print('null'); } } '''); var previewInfo = run({ findNode.statement('if'): NodeChangeForIfStatement() ..conditionValue = false }); // Note: formatting is a little weird here but it's such a rare case that // we don't care. expect(previewInfo.applyTo(code), ''' void f(int i) { while (true) {} } '''); } Future test_eliminateDeadIf_statement_delete_drop_completely_true() async { await analyze(''' void f(int i) { if (i != null) {} else { print('null'); } } '''); var previewInfo = run({ findNode.statement('if'): NodeChangeForIfStatement() ..conditionValue = true }); expect(previewInfo.applyTo(code), ''' void f(int i) {} '''); } Future test_eliminateDeadIf_statement_delete_keep_else() async { await analyze(''' int f(int i) { if (i == null) { return null; } else { return i + 1; } } '''); var previewInfo = run({ findNode.statement('if'): NodeChangeForIfStatement() ..conditionValue = false }); expect(previewInfo.applyTo(code), ''' int f(int i) { return i + 1; } '''); } Future test_eliminateDeadIf_statement_delete_keep_then() async { await analyze(''' int f(int i) { if (i != null) { return i + 1; } else { return null; } } '''); var previewInfo = run({ findNode.statement('if'): NodeChangeForIfStatement() ..conditionValue = true }); expect(previewInfo.applyTo(code), ''' int f(int i) { return i + 1; } '''); } Future test_eliminateDeadIf_statement_delete_keep_then_declaration() async { await analyze(''' void f(int i, String callback()) { if (i != null) { var i = callback(); } else { return; } print(i); } '''); // In this case we have to keep the block so that the scope of `var i` // doesn't widen. var previewInfo = run({ findNode.statement('if'): NodeChangeForIfStatement() ..conditionValue = true }); expect(previewInfo.applyTo(code), ''' void f(int i, String callback()) { { var i = callback(); } print(i); } '''); } Future test_introduceAs_distant_parens_no_longer_needed() async { // Note: in principle it would be nice to delete the outer parens, but it's // difficult to see that they used to be necessary and aren't anymore, so we // leave them. await analyze('f(a, c) => a..b = (throw c..d);'); var cd = findNode.cascade('c..d'); var previewInfo = run({ cd: NodeChangeForExpression() ..addExpressionChange( IntroduceAsChange(nnbdTypeProvider.intType, isDowncast: false), _MockInfo()) }); expect( previewInfo.applyTo(code), 'f(a, c) => a..b = (throw (c..d) as int);'); } Future test_introduceAs_dynamic() async { await analyze('f(Object o) => o;'); var expr = findNode.simple('o;'); var previewInfo = run({ expr: NodeChangeForExpression() ..addExpressionChange( IntroduceAsChange(nnbdTypeProvider.dynamicType, isDowncast: false), _MockInfo()) }); expect(previewInfo.applyTo(code), 'f(Object o) => o as dynamic;'); } Future test_introduceAs_favorPrefix() async { await analyze(''' import 'dart:async' as a; import 'dart:async'; f(Object o) => o; '''); var expr = findNode.simple('o;'); var previewInfo = run({ expr: NodeChangeForExpression() ..addExpressionChange( IntroduceAsChange(nnbdTypeProvider.futureNullType, isDowncast: false), _MockInfo()) }); expect(previewInfo.applyTo(code), ''' import 'dart:async' as a; import 'dart:async'; f(Object o) => o as a.Future; '''); } Future test_introduceAs_functionType() async { await analyze('f(Object o) => o;'); var expr = findNode.simple('o;'); var previewInfo = run({ expr: NodeChangeForExpression() ..addExpressionChange( IntroduceAsChange( FunctionTypeImpl( returnType: nnbdTypeProvider.boolType, typeFormals: [], parameters: [], nullabilitySuffix: NullabilitySuffix.none), isDowncast: false), _MockInfo()) }); expect(previewInfo.applyTo(code), 'f(Object o) => o as bool Function();'); } Future test_introduceAs_functionType_formal_bound() async { await analyze('f(Object o) => o;'); var expr = findNode.simple('o;'); var previewInfo = run({ expr: NodeChangeForExpression() ..addExpressionChange( IntroduceAsChange( FunctionTypeImpl( returnType: nnbdTypeProvider.boolType, typeFormals: [ TypeParameterElementImpl.synthetic('T') ..bound = nnbdTypeProvider.numType ], parameters: [], nullabilitySuffix: NullabilitySuffix.none), isDowncast: false), _MockInfo()) }); expect(previewInfo.applyTo(code), 'f(Object o) => o as bool Function();'); } Future test_introduceAs_functionType_formal_bound_dynamic() async { await analyze('f(Object o) => o;'); var expr = findNode.simple('o;'); var previewInfo = run({ expr: NodeChangeForExpression() ..addExpressionChange( IntroduceAsChange( FunctionTypeImpl( returnType: nnbdTypeProvider.boolType, typeFormals: [ TypeParameterElementImpl.synthetic('T') ..bound = nnbdTypeProvider.dynamicType ], parameters: [], nullabilitySuffix: NullabilitySuffix.none), isDowncast: false), _MockInfo()) }); expect( previewInfo.applyTo(code), 'f(Object o) => o as bool Function();'); } Future test_introduceAs_functionType_formal_bound_object() async { await analyze('f(Object o) => o;'); var expr = findNode.simple('o;'); var previewInfo = run({ expr: NodeChangeForExpression() ..addExpressionChange( IntroduceAsChange( FunctionTypeImpl( returnType: nnbdTypeProvider.boolType, typeFormals: [ TypeParameterElementImpl.synthetic('T') ..bound = nnbdTypeProvider.objectType ], parameters: [], nullabilitySuffix: NullabilitySuffix.none), isDowncast: false), _MockInfo()) }); expect(previewInfo.applyTo(code), 'f(Object o) => o as bool Function();'); } Future test_introduceAs_functionType_formal_bound_object_question() async { await analyze('f(Object o) => o;'); var expr = findNode.simple('o;'); var previewInfo = run({ expr: NodeChangeForExpression() ..addExpressionChange( IntroduceAsChange( FunctionTypeImpl( returnType: nnbdTypeProvider.boolType, typeFormals: [ TypeParameterElementImpl.synthetic('T') ..bound = (nnbdTypeProvider.objectType as TypeImpl) .withNullability(NullabilitySuffix.question) ], parameters: [], nullabilitySuffix: NullabilitySuffix.none), isDowncast: false), _MockInfo()) }); expect( previewInfo.applyTo(code), 'f(Object o) => o as bool Function();'); } Future test_introduceAs_functionType_formal_bound_question() async { await analyze('f(Object o) => o;'); var expr = findNode.simple('o;'); var previewInfo = run({ expr: NodeChangeForExpression() ..addExpressionChange( IntroduceAsChange( FunctionTypeImpl( returnType: nnbdTypeProvider.boolType, typeFormals: [ TypeParameterElementImpl.synthetic('T') ..bound = (nnbdTypeProvider.numType as TypeImpl) .withNullability(NullabilitySuffix.question) ], parameters: [], nullabilitySuffix: NullabilitySuffix.none), isDowncast: false), _MockInfo()) }); expect(previewInfo.applyTo(code), 'f(Object o) => o as bool Function();'); } Future test_introduceAs_functionType_formals() async { await analyze('f(Object o) => o;'); var expr = findNode.simple('o;'); var previewInfo = run({ expr: NodeChangeForExpression() ..addExpressionChange( IntroduceAsChange( FunctionTypeImpl( returnType: nnbdTypeProvider.boolType, typeFormals: [ TypeParameterElementImpl.synthetic('T'), TypeParameterElementImpl.synthetic('U') ], parameters: [], nullabilitySuffix: NullabilitySuffix.none), isDowncast: false), _MockInfo()) }); expect(previewInfo.applyTo(code), 'f(Object o) => o as bool Function();'); } Future test_introduceAs_functionType_parameters() async { await analyze('f(Object o) => o;'); var expr = findNode.simple('o;'); var previewInfo = run({ expr: NodeChangeForExpression() ..addExpressionChange( IntroduceAsChange( FunctionTypeImpl( returnType: nnbdTypeProvider.boolType, typeFormals: [], parameters: [ ParameterElementImpl.synthetic('x', nnbdTypeProvider.intType, ParameterKind.REQUIRED), ParameterElementImpl.synthetic( 'y', nnbdTypeProvider.numType, ParameterKind.REQUIRED) ], nullabilitySuffix: NullabilitySuffix.none), isDowncast: false), _MockInfo()) }); expect(previewInfo.applyTo(code), 'f(Object o) => o as bool Function(int, num);'); } Future test_introduceAs_functionType_parameters_named() async { await analyze('f(Object o) => o;'); var expr = findNode.simple('o;'); var previewInfo = run({ expr: NodeChangeForExpression() ..addExpressionChange( IntroduceAsChange( FunctionTypeImpl( returnType: nnbdTypeProvider.boolType, typeFormals: [], parameters: [ ParameterElementImpl.synthetic( 'x', nnbdTypeProvider.intType, ParameterKind.NAMED), ParameterElementImpl.synthetic( 'y', nnbdTypeProvider.numType, ParameterKind.NAMED) ], nullabilitySuffix: NullabilitySuffix.none), isDowncast: false), _MockInfo()) }); expect(previewInfo.applyTo(code), 'f(Object o) => o as bool Function({int x, num y});'); } Future test_introduceAs_functionType_parameters_optional() async { await analyze('f(Object o) => o;'); var expr = findNode.simple('o;'); var previewInfo = run({ expr: NodeChangeForExpression() ..addExpressionChange( IntroduceAsChange( FunctionTypeImpl( returnType: nnbdTypeProvider.boolType, typeFormals: [], parameters: [ ParameterElementImpl.synthetic('x', nnbdTypeProvider.intType, ParameterKind.POSITIONAL), ParameterElementImpl.synthetic('y', nnbdTypeProvider.numType, ParameterKind.POSITIONAL) ], nullabilitySuffix: NullabilitySuffix.none), isDowncast: false), _MockInfo()) }); expect(previewInfo.applyTo(code), 'f(Object o) => o as bool Function([int, num]);'); } Future test_introduceAs_interfaceType_parameterized() async { await analyze('f(Object o) => o;'); var expr = findNode.simple('o;'); var previewInfo = run({ expr: NodeChangeForExpression() ..addExpressionChange( IntroduceAsChange( nnbdTypeProvider.mapType( nnbdTypeProvider.intType, nnbdTypeProvider.boolType), isDowncast: false), _MockInfo()) }); expect(previewInfo.applyTo(code), 'f(Object o) => o as Map;'); } Future test_introduceAs_no_parens() async { await analyze('f(a, b) => a | b;'); var expr = findNode.binary('a | b'); var previewInfo = run({ expr: NodeChangeForExpression() ..addExpressionChange( IntroduceAsChange(nnbdTypeProvider.intType, isDowncast: false), _MockInfo()) }); expect(previewInfo.applyTo(code), 'f(a, b) => a | b as int;'); } Future test_introduceAs_parens() async { await analyze('f(a, b) => a < b;'); var expr = findNode.binary('a < b'); var previewInfo = run({ expr: NodeChangeForExpression() ..addExpressionChange( IntroduceAsChange(nnbdTypeProvider.boolType, isDowncast: false), _MockInfo()) }); expect(previewInfo.applyTo(code), 'f(a, b) => (a < b) as bool;'); } Future test_introduceAs_usePrefix() async { await analyze(''' import 'dart:async' as a; f(Object o) => o; '''); var expr = findNode.simple('o;'); var previewInfo = run({ expr: NodeChangeForExpression() ..addExpressionChange( IntroduceAsChange(nnbdTypeProvider.futureNullType, isDowncast: false), _MockInfo()) }); expect(previewInfo.applyTo(code), ''' import 'dart:async' as a; f(Object o) => o as a.Future; '''); } Future test_introduceAs_withNullCheck() async { await analyze('f(x) => x;'); var expr = findNode.simple('x;'); var previewInfo = run({ expr: NodeChangeForExpression() ..addExpressionChange(NullCheckChange(MockDartType()), _MockInfo()) ..addExpressionChange( IntroduceAsChange(nnbdTypeProvider.intType, isDowncast: false), _MockInfo()) }); expect(previewInfo.applyTo(code), 'f(x) => x! as int;'); } Future test_keep_redundant_parens() async { await analyze('f(a, b, c) => a + (b * c);'); var previewInfo = run({}); expect(previewInfo, isEmpty); } Future test_makeNullable() async { await analyze('f(int x) {}'); var typeName = findNode.typeName('int'); var previewInfo = run({ typeName: NodeChangeForTypeAnnotation() ..recordNullability( MockDecoratedType( MockDartType(toStringValueWithoutNullability: 'int')), true) }); expect(previewInfo.applyTo(code), 'f(int? x) {}'); } Future test_methodName_change() async { await analyze('f() => f();'); var previewInfo = run({ findNode.methodInvocation('f();').methodName: NodeChangeForMethodName() ..replaceWith('g', null) }); expect(previewInfo.applyTo(code), 'f() => g();'); } Future test_methodName_no_change() async { await analyze('f() => f();'); var previewInfo = run({ findNode.methodInvocation('f();').methodName: NodeChangeForMethodName() }); expect(previewInfo, isNull); } Future test_noChangeToTypeAnnotation() async { await analyze('int x = 0;'); var typeName = findNode.typeName('int'); var previewInfo = run({ typeName: NodeChangeForTypeAnnotation() ..recordNullability( MockDecoratedType( MockDartType(toStringValueWithoutNullability: 'int')), false) }); expect(previewInfo.applyTo(code), 'int x = 0;'); expect(previewInfo.applyTo(code, includeInformative: true), 'int x = 0;'); expect(previewInfo.values.single.single.info.description.appliedMessage, "Type 'int' was not made nullable"); } Future test_noInfoForTypeAnnotation() async { await analyze('int x = 0;'); var typeName = findNode.typeName('int'); var previewInfo = run({typeName: NodeChangeForTypeAnnotation()}); expect(previewInfo, null); } Future test_noValidMigration() async { await analyze('f(a) => null;'); var literal = findNode.nullLiteral('null'); var previewInfo = run({ literal: NodeChangeForExpression() ..addExpressionChange( NoValidMigrationChange(MockDartType()), _MockInfo()) }); expect(previewInfo.applyTo(code), code); expect(previewInfo.applyTo(code, includeInformative: true), 'f(a) => null /* no valid migration */;'); } Future test_nullCheck_index_cascadeResult() async { await analyze('f(a) => a..[0].c;'); var index = findNode.index('[0]'); var previewInfo = run({ index: NodeChangeForExpression() ..addExpressionChange(NullCheckChange(MockDartType()), _MockInfo()) }); expect(previewInfo.applyTo(code), 'f(a) => a..[0]!.c;'); } Future test_nullCheck_methodInvocation_cascadeResult() async { await analyze('f(a) => a..b().c;'); var method = findNode.methodInvocation('b()'); var previewInfo = run({ method: NodeChangeForMethodInvocation() ..addExpressionChange(NullCheckChange(MockDartType()), _MockInfo()) }); expect(previewInfo.applyTo(code), 'f(a) => a..b()!.c;'); } Future test_nullCheck_no_parens() async { await analyze('f(a) => a++;'); var expr = findNode.postfix('a++'); var previewInfo = run({ expr: NodeChangeForExpression() ..addExpressionChange(NullCheckChange(MockDartType()), _MockInfo()) }); expect(previewInfo.applyTo(code), 'f(a) => a++!;'); } Future test_nullCheck_parens() async { await analyze('f(a) => -a;'); var expr = findNode.prefix('-a'); var previewInfo = run({ expr: NodeChangeForExpression() ..addExpressionChange(NullCheckChange(MockDartType()), _MockInfo()) }); expect(previewInfo.applyTo(code), 'f(a) => (-a)!;'); } Future test_nullCheck_propertyAccess_cascadeResult() async { await analyze('f(a) => a..b.c;'); var property = findNode.propertyAccess('b'); var previewInfo = run({ property: NodeChangeForPropertyAccess() ..addExpressionChange(NullCheckChange(MockDartType()), _MockInfo()) }); expect(previewInfo.applyTo(code), 'f(a) => a..b!.c;'); } Future test_parameter_addExplicitType_annotated() async { await analyze('f({@deprecated x = 0}) {}'); var previewInfo = run({ findNode.simpleParameter('x'): NodeChangeForSimpleFormalParameter() ..addExplicitType = nnbdTypeProvider.intType }); expect(previewInfo.applyTo(code), 'f({@deprecated int x = 0}) {}'); } Future test_parameter_addExplicitType_declared_with_covariant() async { await analyze(''' class C { m({num x}) {} } class D extends C { m({covariant x = 3}) {} } '''); var previewInfo = run({ findNode.simpleParameter('x = 3'): NodeChangeForSimpleFormalParameter() ..addExplicitType = nnbdTypeProvider.intType }); expect(previewInfo.applyTo(code), ''' class C { m({num x}) {} } class D extends C { m({covariant int x = 3}) {} } '''); } Future test_parameter_addExplicitType_declared_with_final() async { await analyze('f({final x = 0}) {}'); var previewInfo = run({ findNode.simpleParameter('x'): NodeChangeForSimpleFormalParameter() ..addExplicitType = nnbdTypeProvider.intType }); expect(previewInfo.applyTo(code), 'f({final int x = 0}) {}'); } Future test_parameter_addExplicitType_declared_with_var() async { await analyze('f({var x = 0}) {}'); var previewInfo = run({ findNode.simpleParameter('x'): NodeChangeForSimpleFormalParameter() ..addExplicitType = nnbdTypeProvider.intType }); expect(previewInfo.applyTo(code), 'f({int x = 0}) {}'); } Future test_parameter_addExplicitType_named() async { await analyze('f({x = 0}) {}'); var previewInfo = run({ findNode.simpleParameter('x'): NodeChangeForSimpleFormalParameter() ..addExplicitType = nnbdTypeProvider.intType }); expect(previewInfo.applyTo(code), 'f({int x = 0}) {}'); } Future test_parameter_addExplicitType_no() async { await analyze('f([x = 0]) {}'); var previewInfo = run( {findNode.simpleParameter('x'): NodeChangeForSimpleFormalParameter()}); expect(previewInfo, isNull); } Future test_parameter_addExplicitType_optional_insert() async { await analyze('f([x = 0]) {}'); var previewInfo = run({ findNode.simpleParameter('x'): NodeChangeForSimpleFormalParameter() ..addExplicitType = nnbdTypeProvider.intType }); expect(previewInfo.applyTo(code), 'f([int x = 0]) {}'); } Future test_parameter_addExplicitType_prefixed_type() async { await analyze(''' import 'dart:core' as core; f({x = 0}) {} '''); var previewInfo = run({ findNode.simpleParameter('x'): NodeChangeForSimpleFormalParameter() ..addExplicitType = nnbdTypeProvider.intType }); expect(previewInfo.applyTo(code), ''' import 'dart:core' as core; f({core.int x = 0}) {} '''); } Future test_parameter_field_formal_addExplicitType() async { await analyze(''' class C { int x; C(this.x) {} } '''); var previewInfo = run({ findNode.fieldFormalParameter('this.x'): NodeChangeForFieldFormalParameter() ..addExplicitType = nnbdTypeProvider.intType }); expect(previewInfo.applyTo(code), ''' class C { int x; C(int this.x) {} } '''); } Future test_parameter_field_formal_addExplicitType_declared_with_final() async { await analyze(''' class C { int x; C(final this.x) {} } '''); var previewInfo = run({ findNode.fieldFormalParameter('this.x'): NodeChangeForFieldFormalParameter() ..addExplicitType = nnbdTypeProvider.intType }); expect(previewInfo.applyTo(code), ''' class C { int x; C(final int this.x) {} } '''); } Future test_parameter_field_formal_addExplicitType_declared_with_var() async { await analyze(''' class C { int x; C(var this.x) {} } '''); var previewInfo = run({ findNode.fieldFormalParameter('this.x'): NodeChangeForFieldFormalParameter() ..addExplicitType = nnbdTypeProvider.intType }); expect(previewInfo.applyTo(code), ''' class C { int x; C(int this.x) {} } '''); } Future test_parameter_field_formal_addExplicitType_no() async { await analyze(''' class C { int x; C(this.x) {} } '''); var previewInfo = run({ findNode.fieldFormalParameter('this.x'): NodeChangeForFieldFormalParameter() }); expect(previewInfo, isNull); } Future test_post_increment_add_null_check() async { var content = 'f(int x) => x++;'; await analyze(content); var previewInfo = run({ findNode.postfix('++'): NodeChangeForPostfixExpression() ..addExpressionChange(NullCheckChange(MockDartType()), null) }); expect(previewInfo.applyTo(code), 'f(int x) => x++!;'); } Future test_post_increment_change_target() async { var content = 'f(List x) => x[0]++;'; await analyze(content); var previewInfo = run({ findNode.postfix('++'): NodeChangeForPostfixExpression(), findNode.index('[0]').target: NodeChangeForExpression() ..addExpressionChange(NullCheckChange(MockDartType()), null) }); expect(previewInfo.applyTo(code), 'f(List x) => x![0]++;'); } Future test_post_increment_introduce_as() async { var content = 'f(int x) => x++;'; await analyze(content); var previewInfo = run({ findNode.postfix('++'): NodeChangeForPostfixExpression() ..addExpressionChange( IntroduceAsChange(nnbdTypeProvider.intType, isDowncast: false), null) }); expect(previewInfo.applyTo(code), 'f(int x) => x++ as int;'); } Future test_post_increment_with_bad_combined_type() async { var content = 'f(int x) => x++;'; await analyze(content); var previewInfo = run({ findNode.postfix('++'): NodeChangeForPostfixExpression() ..hasBadCombinedType = true }); expect(previewInfo.applyTo(code), content); expect(previewInfo, hasLength(1)); var edit = previewInfo[content.indexOf('++')].single; expect(edit.info.description, NullabilityFixDescription.compoundAssignmentHasBadCombinedType); expect(edit.isInformative, isTrue); expect(edit.length, '++'.length); } Future test_post_increment_with_nullable_source() async { var content = 'f(int x) => x++;'; await analyze(content); var previewInfo = run({ findNode.postfix('++'): NodeChangeForPostfixExpression() ..hasNullableSource = true }); expect(previewInfo.applyTo(code), content); expect(previewInfo, hasLength(1)); var edit = previewInfo[content.indexOf('++')].single; expect(edit.info.description, NullabilityFixDescription.compoundAssignmentHasNullableSource); expect(edit.isInformative, isTrue); expect(edit.length, '++'.length); } Future test_pre_increment_add_null_check() async { var content = 'f(int x) => ++x;'; await analyze(content); var previewInfo = run({ findNode.prefix('++'): NodeChangeForPrefixExpression() ..addExpressionChange(NullCheckChange(MockDartType()), null) }); expect(previewInfo.applyTo(code), 'f(int x) => (++x)!;'); } Future test_pre_increment_change_target() async { var content = 'f(List x) => ++x[0];'; await analyze(content); var previewInfo = run({ findNode.prefix('++'): NodeChangeForPrefixExpression(), findNode.index('[0]').target: NodeChangeForExpression() ..addExpressionChange(NullCheckChange(MockDartType()), null) }); expect(previewInfo.applyTo(code), 'f(List x) => ++x![0];'); } Future test_pre_increment_introduce_as() async { var content = 'f(int x) => ++x;'; await analyze(content); var previewInfo = run({ findNode.prefix('++'): NodeChangeForPrefixExpression() ..addExpressionChange( IntroduceAsChange(nnbdTypeProvider.intType, isDowncast: false), null) }); expect(previewInfo.applyTo(code), 'f(int x) => ++x as int;'); } Future test_pre_increment_with_bad_combined_type() async { var content = 'f(int x) => ++x;'; await analyze(content); var previewInfo = run({ findNode.prefix('++'): NodeChangeForPrefixExpression() ..hasBadCombinedType = true }); expect(previewInfo.applyTo(code), content); expect(previewInfo, hasLength(1)); var edit = previewInfo[content.indexOf('++')].single; expect(edit.info.description, NullabilityFixDescription.compoundAssignmentHasBadCombinedType); expect(edit.isInformative, isTrue); expect(edit.length, '++'.length); } Future test_pre_increment_with_nullable_source() async { var content = 'f(int x) => ++x;'; await analyze(content); var previewInfo = run({ findNode.prefix('++'): NodeChangeForPrefixExpression() ..hasNullableSource = true }); expect(previewInfo.applyTo(code), content); expect(previewInfo, hasLength(1)); var edit = previewInfo[content.indexOf('++')].single; expect(edit.info.description, NullabilityFixDescription.compoundAssignmentHasNullableSource); expect(edit.isInformative, isTrue); expect(edit.length, '++'.length); } Future test_removeAs_in_cascade_target_no_parens_needed_cascade() async { await analyze('f(a) => ((a..b) as dynamic)..c;'); var cascade = findNode.cascade('a..b'); var cast = cascade.parent.parent; var previewInfo = run({cast: NodeChangeForAsExpression()..removeAs = true}); expect(previewInfo.applyTo(code), 'f(a) => a..b..c;'); } Future test_removeAs_in_cascade_target_no_parens_needed_conditional() async { // TODO(paulberry): would it be better to keep the parens in this case for // clarity, even though they're not needed? await analyze('f(a, b, c) => ((a ? b : c) as dynamic)..d;'); var conditional = findNode.conditionalExpression('a ? b : c'); var cast = conditional.parent.parent; var previewInfo = run({cast: NodeChangeForAsExpression()..removeAs = true}); expect(previewInfo.applyTo(code), 'f(a, b, c) => a ? b : c..d;'); } Future test_removeAs_in_cascade_target_parens_needed_assignment() async { await analyze('f(a, b) => ((a = b) as dynamic)..c;'); var assignment = findNode.assignment('a = b'); var cast = assignment.parent.parent; var previewInfo = run({cast: NodeChangeForAsExpression()..removeAs = true}); expect(previewInfo.applyTo(code), 'f(a, b) => (a = b)..c;'); } Future test_removeAs_in_cascade_target_parens_needed_throw() async { await analyze('f(a) => ((throw a) as dynamic)..b;'); var throw_ = findNode.throw_('throw a'); var cast = throw_.parent.parent; var previewInfo = run({cast: NodeChangeForAsExpression()..removeAs = true}); expect(previewInfo.applyTo(code), 'f(a) => (throw a)..b;'); } Future test_removeAs_lower_precedence_do_not_remove_inner_parens() async { await analyze('f(a, b, c) => (a == b) as Null == c;'); var expr = findNode.binary('a == b'); var previewInfo = run({expr.parent.parent: NodeChangeForAsExpression()..removeAs = true}); expect(previewInfo.applyTo(code), 'f(a, b, c) => (a == b) == c;'); } Future test_removeAs_lower_precedence_remove_inner_parens() async { await analyze('f(a, b) => (a == b) as Null;'); var expr = findNode.binary('a == b'); var previewInfo = run({expr.parent.parent: NodeChangeForAsExpression()..removeAs = true}); expect(previewInfo.applyTo(code), 'f(a, b) => a == b;'); } Future test_removeAs_parens_needed_due_to_cascade() async { // Note: parens are needed, and they could either be around `c..d` or around // `throw c..d`. In an ideal world, we would see that we can just keep the // parens we have, but this is difficult because we don't see that the // parens are needed until we walk far enough up the AST to see that we're // inside a casade expression. So we drop the parens and then create new // ones surrounding `throw c..d`. // // Strictly speaking the code we produce is correct, it's just making a // slightly larger edit than necessary. This is presumably a really rare // corner case so for now we're not worrying about it. await analyze('f(a, c) => a..b = throw (c..d) as int;'); var cd = findNode.cascade('c..d'); var cast = cd.parent.parent; var previewInfo = run({cast: NodeChangeForAsExpression()..removeAs = true}); expect(previewInfo.applyTo(code), 'f(a, c) => a..b = (throw c..d);'); } Future test_removeAs_parens_needed_due_to_cascade_in_conditional_else() async { await analyze('f(a, b, c) => a ? b : (c..d) as int;'); var cd = findNode.cascade('c..d'); var cast = cd.parent.parent; var previewInfo = run({cast: NodeChangeForAsExpression()..removeAs = true}); expect(previewInfo.applyTo(code), 'f(a, b, c) => a ? b : (c..d);'); } Future test_removeAs_parens_needed_due_to_cascade_in_conditional_then() async { await analyze('f(a, b, d) => a ? (b..c) as int : d;'); var bc = findNode.cascade('b..c'); var cast = bc.parent.parent; var previewInfo = run({cast: NodeChangeForAsExpression()..removeAs = true}); expect(previewInfo.applyTo(code), 'f(a, b, d) => a ? (b..c) : d;'); } Future test_removeAs_raise_precedence_do_not_remove_parens() async { await analyze('f(a, b, c) => a | (b | c as int);'); var expr = findNode.binary('b | c'); var previewInfo = run({expr.parent: NodeChangeForAsExpression()..removeAs = true}); expect(previewInfo.applyTo(code), 'f(a, b, c) => a | (b | c);'); } Future test_removeAs_raise_precedence_no_parens_to_remove() async { await analyze('f(a, b, c) => a = b | c as int;'); var expr = findNode.binary('b | c'); var previewInfo = run({expr.parent: NodeChangeForAsExpression()..removeAs = true}); expect(previewInfo.applyTo(code), 'f(a, b, c) => a = b | c;'); } Future test_removeAs_raise_precedence_remove_parens() async { await analyze('f(a, b, c) => a < (b | c as int);'); var expr = findNode.binary('b | c'); var previewInfo = run({expr.parent: NodeChangeForAsExpression()..removeAs = true}); expect(previewInfo.applyTo(code), 'f(a, b, c) => a < b | c;'); } Future test_removeLanguageVersion() async { await analyze(''' //@dart=2.6 void main() {} '''); var previewInfo = run({ findNode.unit: NodeChangeForCompilationUnit() ..removeLanguageVersionComment = true }); // TODO(mfairhurst): Remove beginning \n once it renders properly in preview expect(previewInfo.applyTo(code), '\nvoid main() {}\n'); } Future test_removeLanguageVersion_after_license() async { await analyze(''' // Some licensing stuff here... // Some copyrighting stuff too... // etc... // @dart = 2.6 void main() {} '''); var previewInfo = run({ findNode.unit: NodeChangeForCompilationUnit() ..removeLanguageVersionComment = true }); // TODO(mfairhurst): Remove beginning \n once it renders properly in preview expect(previewInfo.applyTo(code), ''' // Some licensing stuff here... // Some copyrighting stuff too... // etc... void main() {} '''); } Future test_removeLanguageVersion_spaces() async { await analyze(''' // @dart = 2.6 void main() {} '''); var previewInfo = run({ findNode.unit: NodeChangeForCompilationUnit() ..removeLanguageVersionComment = true }); // TODO(mfairhurst): Remove beginning \n once it renders properly in preview expect(previewInfo.applyTo(code), '\nvoid main() {}\n'); } Future test_removeLanguageVersion_withOtherChanges() async { await analyze(''' //@dart=2.6 int f() => null; '''); var previewInfo = run({ findNode.unit: NodeChangeForCompilationUnit() ..removeLanguageVersionComment = true, findNode.typeAnnotation('int'): NodeChangeForTypeAnnotation() ..recordNullability( MockDecoratedType( MockDartType(toStringValueWithoutNullability: 'int')), true) }); // TODO(mfairhurst): Remove beginning \n once it renders properly in preview expect(previewInfo.applyTo(code), '\nint? f() => null;\n'); } Future test_removeNullAwarenessFromMethodInvocation() async { await analyze('f(x) => x?.m();'); var methodInvocation = findNode.methodInvocation('?.'); var previewInfo = run({ methodInvocation: NodeChangeForMethodInvocation() ..removeNullAwareness = true }); expect(previewInfo.applyTo(code), 'f(x) => x.m();'); } Future test_removeNullAwarenessFromMethodInvocation_changeArgument() async { await analyze('f(x) => x?.m(x);'); var methodInvocation = findNode.methodInvocation('?.'); var argument = findNode.simple('x);'); var previewInfo = run({ methodInvocation: NodeChangeForMethodInvocation() ..removeNullAwareness = true, argument: NodeChangeForExpression() ..addExpressionChange(NullCheckChange(MockDartType()), _MockInfo()) }); expect(previewInfo.applyTo(code), 'f(x) => x.m(x!);'); } Future test_removeNullAwarenessFromMethodInvocation_changeTarget() async { await analyze('f(x) => (x as dynamic)?.m();'); var methodInvocation = findNode.methodInvocation('?.'); var cast = findNode.as_('as'); var previewInfo = run({ methodInvocation: NodeChangeForMethodInvocation() ..removeNullAwareness = true, cast: NodeChangeForAsExpression()..removeAs = true }); expect(previewInfo.applyTo(code), 'f(x) => x.m();'); } Future test_removeNullAwarenessFromMethodInvocation_changeTypeArgument() async { await analyze('f(x) => x?.m();'); var methodInvocation = findNode.methodInvocation('?.'); var typeAnnotation = findNode.typeAnnotation('int'); var previewInfo = run({ methodInvocation: NodeChangeForMethodInvocation() ..removeNullAwareness = true, typeAnnotation: NodeChangeForTypeAnnotation() ..recordNullability( MockDecoratedType( MockDartType(toStringValueWithoutNullability: 'int')), true) }); expect(previewInfo.applyTo(code), 'f(x) => x.m();'); } Future test_removeNullAwarenessFromPropertyAccess() async { await analyze('f(x) => x?.y;'); var propertyAccess = findNode.propertyAccess('?.'); var previewInfo = run({ propertyAccess: NodeChangeForPropertyAccess()..removeNullAwareness = true }); expect(previewInfo.applyTo(code), 'f(x) => x.y;'); } Future test_removeNullAwarenessFromPropertyAccess_changeTarget() async { await analyze('f(x) => (x as dynamic)?.y;'); var propertyAccess = findNode.propertyAccess('?.'); var cast = findNode.as_('as'); var previewInfo = run({ propertyAccess: NodeChangeForPropertyAccess()..removeNullAwareness = true, cast: NodeChangeForAsExpression()..removeAs = true }); expect(previewInfo.applyTo(code), 'f(x) => x.y;'); } Future test_requiredAnnotationToRequiredKeyword_leadingAnnotations() async { addMetaPackage(); await analyze(''' import 'package:meta/meta.dart'; f({@deprecated @required int x}) {} '''); var annotation = findNode.annotation('required'); var previewInfo = run({ annotation: NodeChangeForAnnotation()..changeToRequiredKeyword = true }); expect(previewInfo.applyTo(code), ''' import 'package:meta/meta.dart'; f({@deprecated required int x}) {} '''); expect(previewInfo.values.single.single.isDeletion, true); } Future test_requiredAnnotationToRequiredKeyword_prefixed() async { addMetaPackage(); await analyze(''' import 'package:meta/meta.dart' as meta; f({@meta.required int x}) {} '''); var annotation = findNode.annotation('required'); var previewInfo = run({ annotation: NodeChangeForAnnotation()..changeToRequiredKeyword = true }); expect(previewInfo.applyTo(code), ''' import 'package:meta/meta.dart' as meta; f({required int x}) {} '''); expect(previewInfo.values.single.single.isDeletion, true); } Future test_requiredAnnotationToRequiredKeyword_renamed() async { addMetaPackage(); await analyze(''' import 'package:meta/meta.dart'; const foo = required; f({@foo int x}) {} '''); var annotation = findNode.annotation('@foo'); var previewInfo = run({ annotation: NodeChangeForAnnotation()..changeToRequiredKeyword = true }); expect(previewInfo.applyTo(code), ''' import 'package:meta/meta.dart'; const foo = required; f({required int x}) {} '''); } Future test_requiredAnnotationToRequiredKeyword_simple() async { addMetaPackage(); await analyze(''' import 'package:meta/meta.dart'; f({@required int x}) {} '''); var annotation = findNode.annotation('required'); var previewInfo = run({ annotation: NodeChangeForAnnotation()..changeToRequiredKeyword = true }); expect(previewInfo.applyTo(code), ''' import 'package:meta/meta.dart'; f({required int x}) {} '''); expect(previewInfo.values.single.single.isDeletion, true); } Future test_requiredAnnotationToRequiredKeyword_simple_removeViaComment() async { addMetaPackage(); await analyze(''' import 'package:meta/meta.dart'; f({@required int x}) {} '''); var annotation = findNode.annotation('required'); var previewInfo = run( {annotation: NodeChangeForAnnotation()..changeToRequiredKeyword = true}, removeViaComments: true); expect(previewInfo.applyTo(code), ''' import 'package:meta/meta.dart'; f({required int x}) {} '''); expect(previewInfo.values.single.single.isDeletion, true); } Future test_variableDeclarationList_addExplicitType_insert() async { await analyze('final x = 0;'); var previewInfo = run({ findNode.variableDeclarationList('final'): NodeChangeForVariableDeclarationList() ..addExplicitType = nnbdTypeProvider.intType }); expect(previewInfo.applyTo(code), 'final int x = 0;'); } Future test_variableDeclarationList_addExplicitType_metadata() async { await analyze('@deprecated var x = 0;'); var previewInfo = run({ findNode.variableDeclarationList('var'): NodeChangeForVariableDeclarationList() ..addExplicitType = nnbdTypeProvider.intType }); expect(previewInfo.applyTo(code), '@deprecated int x = 0;'); } Future test_variableDeclarationList_addExplicitType_no() async { await analyze('var x = 0;'); var previewInfo = run({ findNode.variableDeclarationList('var'): NodeChangeForVariableDeclarationList() }); expect(previewInfo, isNull); } Future test_variableDeclarationList_addExplicitType_otherPlans() async { await analyze('var x = 0;'); var previewInfo = run({ findNode.variableDeclarationList('var'): NodeChangeForVariableDeclarationList() ..addExplicitType = nnbdTypeProvider.intType, findNode.integerLiteral('0'): NodeChangeForExpression() ..addExpressionChange(NullCheckChange(MockDartType()), _MockInfo()) }); expect(previewInfo.applyTo(code), 'int x = 0!;'); } Future test_variableDeclarationList_addExplicitType_prefixed() async { await analyze(''' import 'dart:core' as core; final x = 0; '''); var previewInfo = run({ findNode.variableDeclarationList('final'): NodeChangeForVariableDeclarationList() ..addExplicitType = nnbdTypeProvider.intType }); expect(previewInfo.applyTo(code), ''' import 'dart:core' as core; final core.int x = 0; '''); } Future test_variableDeclarationList_addExplicitType_replaceVar() async { await analyze('var x = 0;'); var previewInfo = run({ findNode.variableDeclarationList('var'): NodeChangeForVariableDeclarationList() ..addExplicitType = nnbdTypeProvider.intType }); expect(previewInfo.applyTo(code), 'int x = 0;'); } Future test_warnOnDeadIf_false() async { await analyze(''' f(int i) { if (i == null) print(i); } '''); var previewInfo = run({ findNode.statement('if'): NodeChangeForIfStatement() ..conditionValue = false }, warnOnWeakCode: true); expect(previewInfo.applyTo(code, includeInformative: true), ''' f(int i) { if (i == null /* == false */) print(i); } '''); } Future test_warnOnDeadIf_true() async { await analyze(''' f(int i) { if (i != null) print(i); } '''); var previewInfo = run({ findNode.statement('if'): NodeChangeForIfStatement() ..conditionValue = true }, warnOnWeakCode: true); expect(previewInfo.applyTo(code, includeInformative: true), ''' f(int i) { if (i != null /* == true */) print(i); } '''); } Future test_warnOnNullAwareAccess() async { var content = ''' f(int i) { print(i?.isEven); } '''; await analyze(content); var previewInfo = run({ findNode.propertyAccess('?.'): NodeChangeForPropertyAccess() ..removeNullAwareness = true }, warnOnWeakCode: true); expect(previewInfo.applyTo(code), content); expect(previewInfo, hasLength(1)); var edit = previewInfo[content.indexOf('?')].single; expect(edit.isInformative, isTrue); expect(edit.length, '?'.length); } } class FixAggregatorTestBase extends AbstractSingleUnitTest { String code; Future analyze(String code) async { this.code = code; await resolveTestUnit(code); } Map> run(Map changes, {bool removeViaComments = false, bool warnOnWeakCode = false}) { return FixAggregator.run(testUnit, testCode, changes, removeViaComments: removeViaComments, warnOnWeakCode: warnOnWeakCode); } } class MockDartType implements TypeImpl { final String toStringValueWithNullability; final String toStringValueWithoutNullability; const MockDartType( {this.toStringValueWithNullability, this.toStringValueWithoutNullability}); @override String getDisplayString({ bool skipAllDynamicArguments = false, bool withNullability = false, }) { var result = withNullability ? toStringValueWithNullability : toStringValueWithoutNullability; expect(result, isNotNull); return result; } @override noSuchMethod(Invocation invocation) { return super.noSuchMethod(invocation); } } class MockDecoratedType implements DecoratedType { @override final DartType type; const MockDecoratedType(this.type); @override NullabilityNode get node => NullabilityNode.forTypeAnnotation(NullabilityNodeTarget.text('test')); @override noSuchMethod(Invocation invocation) { return super.noSuchMethod(invocation); } } class _MockInfo implements AtomicEditInfo { noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); }