blob: 3be87181ae40645b9759d079c7fe20edde287ba9 [file] [log] [blame]
// 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:analysis_server/src/edit/fix/dartfix_listener.dart';
import 'package:analysis_server/src/edit/fix/non_nullable_fix.dart';
import 'package:analysis_server/src/edit/nnbd_migration/info_builder.dart';
import 'package:analysis_server/src/edit/nnbd_migration/instrumentation_information.dart';
import 'package:analysis_server/src/edit/nnbd_migration/instrumentation_listener.dart';
import 'package:analysis_server/src/edit/nnbd_migration/migration_info.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:meta/meta.dart';
import 'package:nnbd_migration/nnbd_migration.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../../analysis_abstract.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(InfoBuilderTest);
});
}
@reflectiveTest
class InfoBuilderTest extends AbstractAnalysisTest {
/// The information produced by the InfoBuilder, or `null` if [buildInfo] has
/// not yet completed.
List<UnitInfo> infos;
/// Assert various properties of the given [detail].
void assertDetail({@required RegionDetail detail, int offset, int length}) {
if (offset != null) {
expect(detail.target.offset, offset);
}
if (length != null) {
expect(detail.target.length, length);
}
}
/// Assert that some target in [targets] has various properties.
void assertInTargets(
{@required Iterable<NavigationTarget> targets, int offset, int length}) {
String failureReasons = [
if (offset != null) 'offset: $offset',
if (length != null) 'length: $length',
].join(' and ');
expect(targets.any((t) {
return (offset == null || offset == t.offset) &&
(length == null || length == t.length);
}), isTrue, reason: 'Expected one of $targets to contain $failureReasons');
}
/// Assert various properties of the given [region]. If an [offset] is
/// provided but no [length] is provided, a default length of `1` will be
/// used.
void assertRegion(
{@required RegionInfo region,
int offset,
int length,
List<String> details}) {
if (offset != null) {
expect(region.offset, offset);
expect(region.length, length ?? 1);
}
if (details != null) {
expect(region.details.map((detail) => detail.description),
unorderedEquals(details));
}
}
/// Use the InfoBuilder to build information. The information will be stored
/// in [infos].
Future<void> buildInfo() async {
// Compute the analysis results.
server.setAnalysisRoots(
'0', [resourceProvider.pathContext.dirname(testFile)], [], {});
ResolvedUnitResult result = await server
.getAnalysisDriver(testFile)
.currentSession
.getResolvedUnit(testFile);
// Run the migration engine.
DartFixListener listener = DartFixListener(server);
InstrumentationListener instrumentationListener = InstrumentationListener();
NullabilityMigration migration = new NullabilityMigration(
new NullabilityMigrationAdapter(listener),
permissive: false,
instrumentation: instrumentationListener);
migration.prepareInput(result);
migration.processInput(result);
migration.finish();
// Build the migration info.
InstrumentationInformation info = instrumentationListener.data;
InfoBuilder builder = InfoBuilder(info, listener);
infos = (await builder.explainMigration()).toList();
}
/// Uses the InfoBuilder to build information for a single test file.
///
/// Asserts that [originalContent] is migrated to [migratedContent]. Returns
/// the singular UnitInfo which was built.
Future<UnitInfo> buildInfoForSingleTestFile(String originalContent,
{@required String migratedContent}) async {
addTestFile(originalContent);
await buildInfo();
expect(infos, hasLength(1));
UnitInfo unit = infos[0];
expect(unit.path, testFile);
expect(unit.content, migratedContent);
return unit;
}
test_asExpression() async {
UnitInfo unit = await buildInfoForSingleTestFile('''
void f([num a]) {
int b = a as int;
}
''', migratedContent: '''
void f([num? a]) {
int? b = a as int?;
}
''');
List<RegionInfo> regions = unit.regions;
expect(regions, hasLength(3));
// regions[0] is `num? a`.
assertRegion(
region: regions[1],
offset: 24,
details: ["This variable is initialized to a nullable value"]);
assertRegion(
region: regions[2],
offset: 38,
details: ["The value of the expression is nullable"]);
}
test_asExpression_insideReturn() async {
UnitInfo unit = await buildInfoForSingleTestFile('''
int f([num a]) {
return a as int;
}
''', migratedContent: '''
int? f([num? a]) {
return a as int?;
}
''');
List<RegionInfo> regions = unit.regions;
expect(regions, hasLength(3));
// regions[0] is `inf? f`.
// regions[1] is `num? a`.
assertRegion(
region: regions[2],
offset: 36,
details: ["The value of the expression is nullable"]);
}
test_expressionFunctionReturnTarget() async {
UnitInfo unit = await buildInfoForSingleTestFile('''
String g() => 1 == 2 ? "Hello" : null;
''', migratedContent: '''
String? g() => 1 == 2 ? "Hello" : null;
''');
assertInTargets(targets: unit.targets, offset: 7, length: 1); // "g"
assertInTargets(targets: unit.targets, offset: 11, length: 2); // "=>"
List<RegionInfo> regions = unit.regions;
expect(regions, hasLength(1));
assertRegion(
region: regions[0],
offset: 6,
details: ["This function returns a nullable value on line 1"]);
assertDetail(detail: regions[0].details[0], offset: 11, length: 2);
}
test_field_fieldFormalInitializer_optional() async {
UnitInfo unit = await buildInfoForSingleTestFile('''
class A {
int _f;
A([this._f]);
}
''', migratedContent: '''
class A {
int? _f;
A([this._f]);
}
''');
List<RegionInfo> regions = unit.regions;
expect(regions, hasLength(1));
assertRegion(region: regions[0], offset: 15, details: [
"This field is initialized by an optional field formal parameter that "
"has an implicit default value of 'null'"
]);
}
test_field_fieldFormalInitializer_required() async {
UnitInfo unit = await buildInfoForSingleTestFile('''
class A {
int _f;
A(this._f);
}
void g() {
A(null);
}
''', migratedContent: '''
class A {
int? _f;
A(this._f);
}
void g() {
A(null);
}
''');
List<RegionInfo> regions = unit.fixRegions;
expect(regions, hasLength(1));
// TODO(brianwilkerson) It would be nice if the target for the region could
// be the argument rather than the field formal parameter.
assertRegion(region: regions[0], offset: 15, details: [
"This field is initialized by a field formal parameter and a nullable "
"value is passed as an argument"
]);
}
test_field_initializer() async {
UnitInfo unit = await buildInfoForSingleTestFile('''
class A {
int _f = null;
int _f2 = _f;
}
''', migratedContent: '''
class A {
int? _f = null;
int? _f2 = _f;
}
''');
List<RegionInfo> regions = unit.regions;
expect(regions, hasLength(2));
assertRegion(
region: regions[0],
offset: 15,
details: ["This field is initialized to an explicit 'null'"]);
assertRegion(
region: regions[1],
offset: 33,
details: ["This field is initialized to a nullable value"]);
}
test_listAndSetLiteralTypeArgument() async {
// TODO(srawlins): Simplify this test with `var x` once #38341 is fixed.
UnitInfo unit = await buildInfoForSingleTestFile('''
void f() {
String s = null;
List<String> x = <String>["hello", s];
Set<String> y = <String>{"hello", s};
}
''', migratedContent: '''
void f() {
String? s = null;
List<String?> x = <String?>["hello", s];
Set<String?> y = <String?>{"hello", s};
}
''');
List<RegionInfo> regions = unit.fixRegions;
expect(regions, hasLength(5));
// regions[0] is the `String? s` fix.
// regions[1] is the `List<String?> x` fix.
assertRegion(
region: regions[2],
offset: 58,
details: ["This list is initialized with a nullable value on line 3"]);
assertDetail(detail: regions[2].details[0], offset: 67, length: 1);
// regions[3] is the `Set<String?> y` fix.
assertRegion(
region: regions[4],
offset: 100,
details: ["This set is initialized with a nullable value on line 4"]);
assertDetail(detail: regions[4].details[0], offset: 107, length: 1);
}
test_listLiteralTypeArgument_collectionIf() async {
// TODO(srawlins): Simplify this test with `var x` once #38341 is fixed.
UnitInfo unit = await buildInfoForSingleTestFile('''
void f() {
String s = null;
List<String> x = <String>[
"hello",
if (1 == 2) s
];
}
''', migratedContent: '''
void f() {
String? s = null;
List<String?> x = <String?>[
"hello",
if (1 == 2) s
];
}
''');
List<RegionInfo> regions = unit.fixRegions;
expect(regions, hasLength(3));
// regions[0] is the `String? s` fix.
// regions[1] is the `List<String?> x` fix.
assertRegion(
region: regions[2],
offset: 58,
details: ["This list is initialized with a nullable value on line 5"]);
assertDetail(detail: regions[2].details[0], offset: 88, length: 1);
}
test_localVariable() async {
UnitInfo unit = await buildInfoForSingleTestFile('''
void f() {
int _v1 = null;
int _v2 = _v1;
}
''', migratedContent: '''
void f() {
int? _v1 = null;
int? _v2 = _v1;
}
''');
List<RegionInfo> regions = unit.regions;
expect(regions, hasLength(2));
assertRegion(
region: regions[0],
offset: 16,
details: ["This variable is initialized to an explicit 'null'"]);
assertRegion(
region: regions[1],
offset: 35,
details: ["This variable is initialized to a nullable value"]);
}
test_mapLiteralTypeArgument() async {
// TODO(srawlins): Simplify this test with `var x` once #38341 is fixed.
UnitInfo unit = await buildInfoForSingleTestFile('''
void f() {
String s = null;
Map<String, bool> x = <String, bool>{"hello": false, s: true};
Map<bool, String> y = <bool, String>{false: "hello", true: s};
}
''', migratedContent: '''
void f() {
String? s = null;
Map<String?, bool> x = <String?, bool>{"hello": false, s: true};
Map<bool, String?> y = <bool, String?>{false: "hello", true: s};
}
''');
List<RegionInfo> regions = unit.fixRegions;
expect(regions, hasLength(5));
// regions[0] is the `String? s` fix.
// regions[1] is the `Map<String?, bool> x` fix.
assertRegion(
region: regions[2],
offset: 63,
details: ["This map is initialized with a nullable value on line 3"]);
assertDetail(detail: regions[2].details[0], offset: 85, length: 1);
// regions[3] is the `Map<bool, String?> y` fix.
assertRegion(
region: regions[4],
offset: 136,
details: ["This map is initialized with a nullable value on line 4"]);
assertDetail(detail: regions[4].details[0], offset: 156, length: 1);
}
test_nonNullableType_assert() async {
UnitInfo unit = await buildInfoForSingleTestFile('''
void f(String s) {
assert(s != null);
}
''', migratedContent: '''
void f(String s) {
assert(s != null);
}
''');
List<RegionInfo> regions = unit.nonNullableTypeRegions;
expect(regions, hasLength(1));
assertRegion(
region: regions[0],
offset: 7,
length: 6,
details: ["This value is asserted to be non-null"]);
}
test_nonNullableType_exclamationComment() async {
UnitInfo unit = await buildInfoForSingleTestFile('''
void f(String /*!*/ s) {}
''', migratedContent: '''
void f(String /*!*/ s) {}
''');
List<RegionInfo> regions = unit.nonNullableTypeRegions;
expect(regions, hasLength(1));
assertRegion(region: regions[0], offset: 7, length: 6, details: [
'This type is annotated with a non-nullability comment ("/*!*/")'
]);
}
test_nonNullableType_unconditionalFieldAccess() async {
UnitInfo unit = await buildInfoForSingleTestFile('''
void f(String s) {
print(s.length);
}
''', migratedContent: '''
void f(String s) {
print(s.length);
}
''');
List<RegionInfo> regions = unit.nonNullableTypeRegions;
expect(regions, hasLength(1));
assertRegion(region: regions[0], offset: 7, length: 6, details: [
"This value is unconditionally used in a non-nullable context"
]);
}
test_parameter_fromInvocation_explicit() async {
UnitInfo unit = await buildInfoForSingleTestFile('''
void f(String s) {}
void g() {
f(null);
}
''', migratedContent: '''
void f(String? s) {}
void g() {
f(null);
}
''');
List<RegionInfo> regions = unit.regions;
expect(regions, hasLength(1));
assertRegion(
region: regions[0],
offset: 13,
details: ["An explicit 'null' is passed as an argument"]);
}
@failingTest
test_parameter_fromInvocation_implicit() async {
// Failing because the upstream edge ("always -(hard)-> type(13)")
// associated with the reason (a _NullabilityNodeSimple) had a `null` origin
// when the listener's `graphEdge` method was called.
UnitInfo unit = await buildInfoForSingleTestFile('''
void f(String s) {}
void g(p) {
f(p);
}
''', migratedContent: '''
void f(String? s) {}
void g(p) {
f(p);
}
''');
List<RegionInfo> regions = unit.regions;
expect(regions, hasLength(1));
assertRegion(
region: regions[0],
offset: 13,
details: ["A nullable value is explicitly passed as an argument"]);
}
test_parameter_fromOverriden_explicit() async {
UnitInfo unit = await buildInfoForSingleTestFile('''
class A {
void m(int p) {}
}
class B extends A {
void m(Object p) {}
}
void f(A a) {
a.m(null);
}
''', migratedContent: '''
class A {
void m(int? p) {}
}
class B extends A {
void m(Object? p) {}
}
void f(A a) {
a.m(null);
}
''');
List<RegionInfo> regions = unit.fixRegions;
expect(regions, hasLength(2));
assertRegion(
region: regions[0],
offset: 22,
details: ["An explicit 'null' is passed as an argument"]);
assertRegion(region: regions[1], offset: 67, details: [
"The corresponding parameter in the overridden method is nullable"
]);
}
test_parameter_fromOverriden_implicit() async {
UnitInfo unit = await buildInfoForSingleTestFile('''
class A {
void m(p) {}
}
class B extends A {
void m(Object p) {}
}
''', migratedContent: '''
class A {
void m(p) {}
}
class B extends A {
void m(Object? p) {}
}
''');
List<RegionInfo> regions = unit.fixRegions;
expect(regions, hasLength(1));
// TODO(brianwilkerson) The detail should read something like
// "The overridden method accepts a nullable type"
assertRegion(
region: regions[0],
offset: 62,
details: ["A nullable value is assigned"]);
}
test_parameter_named_omittedInCall() async {
UnitInfo unit = await buildInfoForSingleTestFile('''
void f() { g(); }
void g({int i}) {}
''', migratedContent: '''
void f() { g(); }
void g({int? i}) {}
''');
List<RegionInfo> regions = unit.fixRegions;
expect(regions, hasLength(1));
assertRegion(region: regions[0], offset: 30, details: [
"This named parameter was omitted in a call to this function",
"This parameter has an implicit default value of 'null'",
]);
assertDetail(detail: regions[0].details[0], offset: 11, length: 3);
}
@failingTest
test_parameter_optional_explicitDefault_null() async {
// Failing because we appear to never get an origin when the upstream node
// for an edge is 'always'.
UnitInfo unit = await buildInfoForSingleTestFile('''
void f({String s = null}) {}
''', migratedContent: '''
void f({String? s = null}) {}
''');
List<RegionInfo> regions = unit.fixRegions;
expect(regions, hasLength(1));
assertRegion(
region: regions[0],
offset: 14,
details: ["This parameter has an explicit default value of 'null'"]);
}
@failingTest
test_parameter_optional_explicitDefault_nullable() async {
// Failing because we appear to never get an origin when the upstream node
// for an edge is 'always'.
UnitInfo unit = await buildInfoForSingleTestFile('''
const sd = null;
void f({String s = sd}) {}
''', migratedContent: '''
const sd = null;
void f({String? s = sd}) {}
''');
List<RegionInfo> regions = unit.fixRegions;
expect(regions, hasLength(1));
assertRegion(
region: regions[0],
offset: 31,
details: ["This parameter has an explicit default value of 'null'"]);
}
test_parameter_optional_implicitDefault_named() async {
UnitInfo unit = await buildInfoForSingleTestFile('''
void f({String s}) {}
''', migratedContent: '''
void f({String? s}) {}
''');
List<RegionInfo> regions = unit.fixRegions;
expect(regions, hasLength(1));
assertRegion(
region: regions[0],
offset: 14,
details: ["This parameter has an implicit default value of 'null'"]);
}
test_parameter_optional_implicitDefault_positional() async {
UnitInfo unit = await buildInfoForSingleTestFile('''
void f([String s]) {}
''', migratedContent: '''
void f([String? s]) {}
''');
List<RegionInfo> regions = unit.fixRegions;
expect(regions, hasLength(1));
assertRegion(
region: regions[0],
offset: 14,
details: ["This parameter has an implicit default value of 'null'"]);
}
@failingTest
test_return_fromOverriden() async {
UnitInfo unit = await buildInfoForSingleTestFile('''
abstract class A {
String m();
}
class B implements A {
String m() => 1 == 2 ? "Hello" : null;
}
''', migratedContent: '''
abstract class A {
String? m();
}
class B implements A {
String? m() => 1 == 2 ? "Hello" : null;
}
''');
List<RegionInfo> regions = unit.fixRegions;
expect(regions, hasLength(2));
assertRegion(
region: regions[0],
offset: 27,
details: ["An overridding method has a nullable return value"]);
}
test_return_multipleReturns() async {
UnitInfo unit = await buildInfoForSingleTestFile('''
String g() {
int x = 1;
if (x == 2) return x == 3 ? "Hello" : null;
return "Hello";
}
''', migratedContent: '''
String? g() {
int x = 1;
if (x == 2) return x == 3 ? "Hello" : null;
return "Hello";
}
''');
List<RegionInfo> regions = unit.fixRegions;
expect(regions, hasLength(1));
assertRegion(
region: regions[0],
offset: 6,
details: ["This function returns a nullable value on line 3"]);
assertInTargets(targets: unit.targets, offset: 40, length: 6); // "return"
}
test_returnDetailTarget() async {
UnitInfo unit = await buildInfoForSingleTestFile('''
String g() {
return 1 == 2 ? "Hello" : null;
}
''', migratedContent: '''
String? g() {
return 1 == 2 ? "Hello" : null;
}
''');
assertInTargets(targets: unit.targets, offset: 7, length: 1); // "g"
assertInTargets(targets: unit.targets, offset: 15, length: 6); // "return"
List<RegionInfo> regions = unit.regions;
expect(regions, hasLength(1));
assertRegion(
region: regions[0],
offset: 6,
details: ["This function returns a nullable value on line 2"]);
assertDetail(detail: regions[0].details[0], offset: 15, length: 6);
}
test_returnType_function_expression() async {
UnitInfo unit = await buildInfoForSingleTestFile('''
int _f = null;
int f() => _f;
''', migratedContent: '''
int? _f = null;
int? f() => _f;
''');
List<RegionInfo> regions = unit.regions;
expect(regions, hasLength(2));
assertRegion(
region: regions[0],
offset: 3,
details: ["This variable is initialized to an explicit 'null'"]);
assertRegion(
region: regions[1],
offset: 19,
details: ["This function returns a nullable value on line 2"]);
}
test_returnType_getter_block() async {
UnitInfo unit = await buildInfoForSingleTestFile('''
class A {
int _f = null;
int get f {
return _f;
}
}
''', migratedContent: '''
class A {
int? _f = null;
int? get f {
return _f;
}
}
''');
List<RegionInfo> regions = unit.regions;
expect(regions, hasLength(2));
assertRegion(
region: regions[0],
offset: 15,
details: ["This field is initialized to an explicit 'null'"]);
assertRegion(
region: regions[1],
offset: 33,
details: ["This getter returns a nullable value on line 4"]);
}
test_returnType_getter_expression() async {
UnitInfo unit = await buildInfoForSingleTestFile('''
class A {
int _f = null;
int get f => _f;
}
''', migratedContent: '''
class A {
int? _f = null;
int? get f => _f;
}
''');
List<RegionInfo> regions = unit.regions;
expect(regions, hasLength(2));
assertRegion(
region: regions[0],
offset: 15,
details: ["This field is initialized to an explicit 'null'"]);
assertRegion(
region: regions[1],
offset: 33,
details: ["This getter returns a nullable value on line 3"]);
}
test_setLiteralTypeArgument_nestedList() async {
// TODO(srawlins): Simplify this test with `var x` once #38341 is fixed.
UnitInfo unit = await buildInfoForSingleTestFile('''
void f() {
String s = null;
Set<List<String>> x = <List<String>>{
["hello"],
if (1 == 2) [s]
};
}
''', migratedContent: '''
void f() {
String? s = null;
Set<List<String?>> x = <List<String?>>{
["hello"],
if (1 == 2) [s]
};
}
''');
List<RegionInfo> regions = unit.fixRegions;
expect(regions, hasLength(3));
// regions[0] is the `String? s` fix.
// regions[1] is the `Set<List<String?>> x` fix.
assertRegion(
region: regions[2],
offset: 68,
details: ["This set is initialized with a nullable value on line 5"]);
// TODO(srawlins): Actually, this is marking the `[s]`, but I think only
// `s` should be marked. Minor bug for now.
assertDetail(detail: regions[2].details[0], offset: 101, length: 3);
}
test_topLevelVariable() async {
UnitInfo unit = await buildInfoForSingleTestFile('''
int _f = null;
int _f2 = _f;
''', migratedContent: '''
int? _f = null;
int? _f2 = _f;
''');
List<RegionInfo> regions = unit.fixRegions;
expect(regions, hasLength(2));
assertRegion(
region: regions[0],
offset: 3,
details: ["This variable is initialized to an explicit 'null'"]);
assertRegion(
region: regions[1],
offset: 19,
details: ["This variable is initialized to a nullable value"]);
}
}