blob: 43fd177fea270f776d54524eb96636b6d3ef1b1f [file] [log] [blame]
// Copyright (c) 2020, 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/file_system/memory_file_system.dart';
import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart'
hide NavigationTarget;
import 'package:nnbd_migration/nnbd_migration.dart';
import 'package:nnbd_migration/src/front_end/dartfix_listener.dart';
import 'package:nnbd_migration/src/front_end/migration_info.dart';
import 'package:nnbd_migration/src/front_end/migration_state.dart';
import 'package:nnbd_migration/src/front_end/offset_mapper.dart';
import 'package:nnbd_migration/src/front_end/path_mapper.dart';
import 'package:nnbd_migration/src/preview/preview_site.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../front_end/nnbd_migration_test_base.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(PreviewSiteTest);
defineReflectiveTests(PreviewSiteWithEngineTest);
});
}
@reflectiveTest
class PreviewSiteTest with ResourceProviderMixin, PreviewSiteTestMixin {
@override
Future<void> performEdit(String path, int offset, String replacement) {
final pathUri = Uri.file(path).path;
return site
.performEdit(Uri.parse('localhost://$pathUri?offset=$offset&end=$offset'
'&replacement=${Uri.encodeComponent(replacement)}'));
}
void setUp() {
dartfixListener =
DartFixListener(null, _exceptionReported, _exceptionReported);
resourceProvider = MemoryResourceProvider();
final migrationInfo = MigrationInfo({}, {}, null, null);
state = MigrationState(null, null, dartfixListener, null);
state.pathMapper = PathMapper(resourceProvider);
state.migrationInfo = migrationInfo;
site = PreviewSite(state, () async {
return state;
}, () {});
}
void test_apply_regress41391() async {
final path = convertPath('/test.dart');
final file = getFile(path);
final analysisOptionsPath = convertPath('/analysis_options.yaml');
final analysisOptions = getFile(analysisOptionsPath);
analysisOptions.writeAsStringSync('analyzer:');
final content = 'void main() {}';
file.writeAsStringSync(content);
site.unitInfoMap[path] = UnitInfo(path)..diskContent = content;
// Add a source change for analysis_options, which has no UnitInfo.
dartfixListener.addSourceFileEdit(
'enable experiment',
Location(analysisOptionsPath, 9, 0, 1, 9),
SourceFileEdit(analysisOptionsPath, 0, edits: [
SourceEdit(9, 0, '\n enable-experiment:\n - non-nullable')
]));
// This should not crash.
site.performApply();
expect(analysisOptions.readAsStringSync(), '''
analyzer:
enable-experiment:
- non-nullable''');
expect(state.hasBeenApplied, true);
}
void test_applyChangesEmpty() {
final file = getFile('/test.dart');
file.writeAsStringSync('void main() {}');
site.performApply();
expect(file.readAsStringSync(), 'void main() {}');
expect(state.hasBeenApplied, true);
}
void test_applyChangesTwiceThrows() {
site.performApply();
expect(site.performApply, throwsA(isA<StateError>()));
}
void test_applyMigration_sanityCheck_dontApply() async {
final path = convertPath('/test.dart');
final file = getFile(path);
site.unitInfoMap[path] = UnitInfo(path)
..diskContent = '// different content';
final currentContent = 'void main() {}';
file.writeAsStringSync(currentContent);
dartfixListener.addSourceFileEdit(
'test change',
Location(path, 10, 0, 1, 10),
SourceFileEdit(path, 0, edits: [SourceEdit(10, 0, 'List args')]));
expect(() => site.performApply(), throwsA(isA<StateError>()));
expect(file.readAsStringSync(), currentContent);
expect(state.hasBeenApplied, false);
}
void test_applyMultipleChanges() {
final path = convertPath('/test.dart');
final file = getFile(path);
final content = 'void main() {}';
file.writeAsStringSync(content);
site.unitInfoMap[path] = UnitInfo(path)..diskContent = content;
dartfixListener.addSourceFileEdit(
'test change',
Location(path, 10, 0, 1, 10),
SourceFileEdit(path, 0, edits: [
SourceEdit(10, 0, 'List args'),
SourceEdit(13, 0, '\n print(args);\n')
]));
site.performApply();
expect(file.readAsStringSync(), '''
void main(List args) {
print(args);
}''');
expect(state.hasBeenApplied, true);
}
void test_applySingleChange() {
final path = convertPath('/test.dart');
final file = getFile(path);
final content = 'void main() {}';
file.writeAsStringSync(content);
site.unitInfoMap[path] = UnitInfo(path)..diskContent = content;
dartfixListener.addSourceFileEdit(
'test change',
Location(path, 10, 0, 1, 10),
SourceFileEdit(path, 0, edits: [SourceEdit(10, 0, 'List args')]));
site.performApply();
expect(file.readAsStringSync(), 'void main(List args) {}');
expect(state.hasBeenApplied, true);
}
void test_performEdit() {
final path = convertPath('/test.dart');
final file = getFile(path);
final content = 'int foo() {}';
final unitInfo = UnitInfo(path)
..diskContent = content
..content = content
..diskChangesOffsetMapper = OffsetMapper.identity;
site.unitInfoMap[path] = unitInfo;
file.writeAsStringSync(content);
performEdit(path, 3, '/*?*/');
expect(file.readAsStringSync(), 'int/*?*/ foo() {}');
expect(state.hasBeenApplied, false);
expect(state.needsRerun, true);
expect(unitInfo.content, 'int/*?*/ foo() {}');
}
void test_performEdit_sanityCheck_dontApply() {
final path = convertPath('/test.dart');
final file = getFile(path);
site.unitInfoMap[path] = UnitInfo(path)
..diskContent = '// different content';
final currentContent = 'void main() {}';
file.writeAsStringSync(currentContent);
expect(() => performEdit(path, 0, 'foo'), throwsA(isA<StateError>()));
expect(file.readAsStringSync(), currentContent);
expect(state.hasBeenApplied, false);
}
void _exceptionReported(String detail) {
fail('Unexpected error during migration: $detail');
}
}
mixin PreviewSiteTestMixin {
PreviewSite site;
DartFixListener dartfixListener;
MigrationState state;
Future<void> performEdit(String path, int offset, String replacement) {
final pathUri = Uri.file(path).path;
return site
.performEdit(Uri.parse('localhost://$pathUri?offset=$offset&end=$offset'
'&replacement=${Uri.encodeComponent(replacement)}'));
}
}
@reflectiveTest
class PreviewSiteWithEngineTest extends NnbdMigrationTestBase
with ResourceProviderMixin, PreviewSiteTestMixin {
@override
void setUp() {
super.setUp();
dartfixListener =
DartFixListener(null, _exceptionReported, _exceptionReported);
final migrationInfo = MigrationInfo({}, {}, null, null);
state = MigrationState(null, null, dartfixListener, null);
nodeMapper = state.nodeMapper;
state.pathMapper = PathMapper(resourceProvider);
state.migrationInfo = migrationInfo;
site = PreviewSite(state, () async {
return state;
}, () {});
}
void test_applyHintAction() async {
final path = convertPath('/home/tests/bin/test.dart');
final file = getFile(path);
final content = r'''
int x;
int y = x;
''';
file.writeAsStringSync(content);
final migratedContent = '''
int? x;
int? y = x;
''';
final unitInfo = await buildInfoForSingleTestFile(content,
migratedContent: migratedContent);
site.unitInfoMap[path] = unitInfo;
await site.performHintAction(
unitInfo.regions[1].traces[0].entries[0].hintActions[0]);
await site.performHintAction(
unitInfo.regions[1].traces[0].entries[2].hintActions[0]);
expect(file.readAsStringSync(), '''
int/*?*/ x;
int/*?*/ y = x;
''');
expect(unitInfo.content, '''
int/*?*/? x;
int/*?*/? y = x;
''');
assertRegion(
region: unitInfo.regions[0], offset: unitInfo.content.indexOf('? x'));
assertRegion(
region: unitInfo.regions[1], offset: unitInfo.content.indexOf('? y'));
final targets = List<NavigationTarget>.from(unitInfo.targets);
assertInTargets(
targets: targets,
offset: unitInfo.content.indexOf('x'),
offsetMapper: unitInfo.offsetMapper);
assertInTargets(
targets: targets,
offset: unitInfo.content.indexOf('y'),
offsetMapper: unitInfo.offsetMapper);
var trace = unitInfo.regions[1].traces[0];
assertTraceEntry(unitInfo, trace.entries[0], 'y',
unitInfo.content.indexOf('int/*?*/? y'), contains('y (test.dart:2:1)'));
assertTraceEntry(unitInfo, trace.entries[1], 'y',
unitInfo.content.indexOf('= x;') + '= '.length, contains('data flow'));
expect(state.hasBeenApplied, false);
expect(state.needsRerun, true);
}
void test_applyHintAction_removeHint() async {
final path = convertPath('/home/tests/bin/test.dart');
final file = getFile(path);
final content = r'''
int/*!*/ x;
int y = x;
''';
file.writeAsStringSync(content);
final migratedContent = '''
int/*!*/ x;
int y = x;
''';
final unitInfo = await buildInfoForSingleTestFile(content,
migratedContent: migratedContent);
site.unitInfoMap[path] = unitInfo;
await site.performHintAction(
unitInfo.regions[0].traces[0].entries[0].hintActions[0]);
expect(file.readAsStringSync(), '''
int x;
int y = x;
''');
expect(unitInfo.content, '''
int x;
int y = x;
''');
expect(unitInfo.regions, hasLength(1));
assertRegion(
kind: NullabilityFixKind.typeNotMadeNullable,
region: unitInfo.regions[0],
offset: unitInfo.content.indexOf(' y'));
final targets = List<NavigationTarget>.from(unitInfo.targets);
assertInTargets(
targets: targets,
offset: unitInfo.content.indexOf('x'),
offsetMapper: unitInfo.offsetMapper);
expect(state.hasBeenApplied, false);
expect(state.needsRerun, true);
}
void test_performEdit_multiple() async {
final path = convertPath('/test.dart');
final file = getFile(path);
final content = r'''
int x;
int y = x;
''';
file.writeAsStringSync(content);
final migratedContent = '''
int? x;
int? y = x;
''';
final unitInfo = await buildInfoForSingleTestFile(content,
migratedContent: migratedContent);
site.unitInfoMap[path] = unitInfo;
final firstEditOffset = unitInfo.regions[0].edits[0].offset;
performEdit(path, firstEditOffset, '/*?*/');
final secondEditOffset = unitInfo.regions[1].edits[0].offset;
performEdit(path, secondEditOffset, '/*?*/');
expect(file.readAsStringSync(), '''
int/*?*/ x;
int/*?*/ y = x;
''');
expect(unitInfo.content, '''
int/*?*/? x;
int/*?*/? y = x;
''');
assertRegion(
region: unitInfo.regions[0], offset: unitInfo.content.indexOf('? x'));
assertRegion(
region: unitInfo.regions[1], offset: unitInfo.content.indexOf('? y'));
final targets = List<NavigationTarget>.from(unitInfo.targets);
assertInTargets(
targets: targets,
offset: unitInfo.content.indexOf('x'),
offsetMapper: unitInfo.offsetMapper);
assertInTargets(
targets: targets,
offset: unitInfo.content.indexOf('y'),
offsetMapper: unitInfo.offsetMapper);
var trace = unitInfo.regions[1].traces[0];
assertTraceEntry(unitInfo, trace.entries[0], 'y',
unitInfo.content.indexOf('int/*?*/? y'), contains('y (test.dart:2:1)'));
assertTraceEntry(unitInfo, trace.entries[1], 'y',
unitInfo.content.indexOf('= x;') + '= '.length, contains('data flow'));
expect(state.hasBeenApplied, false);
expect(state.needsRerun, true);
}
void _exceptionReported(String detail) {
fail('Unexpected error during migration: $detail');
}
}