Migration: Fix reporting of exceptions.
Permissive mode in the analyzer currently is broken, leading to a
useless stacktrace if an exception occurs. As a quick fix, turn
permissive mode off, so the exception just bubbles up normally and
shows on the console. In follow-up CLs I'll actually fix permissive
mode and add a user option to enable it.
Fixes #42138.
Change-Id: I59322ca9dd90b856c56a6ec335762b94ec6935b7
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/149863
Reviewed-by: Mike Fairhurst <mfairhurst@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/nnbd_migration/lib/migration_cli.dart b/pkg/nnbd_migration/lib/migration_cli.dart
index 3a0a4af..d0494cd 100644
--- a/pkg/nnbd_migration/lib/migration_cli.dart
+++ b/pkg/nnbd_migration/lib/migration_cli.dart
@@ -257,6 +257,19 @@
return stream.first;
}
+ NonNullableFix createNonNullableFix(DartFixListener listener,
+ ResourceProvider resourceProvider, LineInfo getLineInfo(String path),
+ {List<String> included = const <String>[],
+ int preferredPort,
+ bool enablePreview = true,
+ String summaryPath}) {
+ return NonNullableFix(listener, resourceProvider, getLineInfo,
+ included: included,
+ preferredPort: preferredPort,
+ enablePreview: enablePreview,
+ summaryPath: summaryPath);
+ }
+
/// Parses and validates command-line arguments, and stores the results in
/// [options].
///
@@ -365,7 +378,7 @@
_fixCodeProcessor = _FixCodeProcessor(context, this);
_dartFixListener =
DartFixListener(DriverProviderImpl(resourceProvider, context));
- nonNullableFix = NonNullableFix(
+ nonNullableFix = createNonNullableFix(
_dartFixListener, resourceProvider, _fixCodeProcessor.getLineInfo,
included: [options.directory],
preferredPort: options.previewPort,
diff --git a/pkg/nnbd_migration/lib/src/front_end/non_nullable_fix.dart b/pkg/nnbd_migration/lib/src/front_end/non_nullable_fix.dart
index 001ee1f..ed37a85 100644
--- a/pkg/nnbd_migration/lib/src/front_end/non_nullable_fix.dart
+++ b/pkg/nnbd_migration/lib/src/front_end/non_nullable_fix.dart
@@ -26,9 +26,8 @@
/// and determines whether the associated variable or parameter can be null
/// then adds or removes a '?' trailing the named type as appropriate.
class NonNullableFix {
- /// TODO(paulberry): stop using permissive mode once the migration logic is
- /// mature enough.
- static const bool _usePermissiveMode = true;
+ /// TODO(paulberry): allow this to be controlled by a command-line parameter.
+ static const bool _usePermissiveMode = false;
// TODO(srawlins): Refactor to use
// `Feature.non_nullable.firstSupportedVersion` when this becomes non-null.
@@ -101,6 +100,10 @@
int get numPhases => 3;
+ InstrumentationListener createInstrumentationListener(
+ {MigrationSummary migrationSummary}) =>
+ InstrumentationListener(migrationSummary: migrationSummary);
+
Future<void> finish() async {
migration.finish();
final state = MigrationState(
@@ -188,7 +191,7 @@
}
void reset() {
- instrumentationListener = InstrumentationListener(
+ instrumentationListener = createInstrumentationListener(
migrationSummary: summaryPath == null
? null
: MigrationSummary(summaryPath, resourceProvider, includedRoot));
diff --git a/pkg/nnbd_migration/test/migration_cli_test.dart b/pkg/nnbd_migration/test/migration_cli_test.dart
index f86b9a9..019bb30 100644
--- a/pkg/nnbd_migration/test/migration_cli_test.dart
+++ b/pkg/nnbd_migration/test/migration_cli_test.dart
@@ -6,14 +6,21 @@
import 'dart:convert';
import 'dart:io';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/file_system/file_system.dart' show ResourceProvider;
import 'package:analyzer/file_system/memory_file_system.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
+import 'package:analyzer/source/line_info.dart';
import 'package:analyzer/src/test_utilities/mock_sdk.dart' as mock_sdk;
import 'package:args/args.dart';
import 'package:cli_util/cli_logging.dart';
import 'package:http/http.dart' as http;
import 'package:meta/meta.dart';
+import 'package:nnbd_migration/instrumentation.dart';
import 'package:nnbd_migration/migration_cli.dart';
+import 'package:nnbd_migration/src/front_end/dartfix_listener.dart';
+import 'package:nnbd_migration/src/front_end/instrumentation_listener.dart';
+import 'package:nnbd_migration/src/front_end/migration_summary.dart';
import 'package:nnbd_migration/src/front_end/non_nullable_fix.dart';
import 'package:nnbd_migration/src/front_end/web/edit_details.dart';
import 'package:nnbd_migration/src/front_end/web/file_details.dart';
@@ -29,10 +36,54 @@
});
}
+/// Specialization of [InstrumentationListener] that generates artificial
+/// exceptions, so that we can test they are properly propagated to top level.
+class _ExceptionGeneratingInstrumentationListener
+ extends InstrumentationListener {
+ _ExceptionGeneratingInstrumentationListener(
+ {MigrationSummary migrationSummary})
+ : super(migrationSummary: migrationSummary);
+
+ @override
+ void externalDecoratedType(Element element, DecoratedTypeInfo decoratedType) {
+ if (element.name == 'print') {
+ throw StateError('Artificial exception triggered');
+ }
+ super.externalDecoratedType(element, decoratedType);
+ }
+}
+
+/// Specialization of [NonNullableFix] that generates artificial exceptions, so
+/// that we can test they are properly propagated to top level.
+class _ExceptionGeneratingNonNullableFix extends NonNullableFix {
+ _ExceptionGeneratingNonNullableFix(DartFixListener listener,
+ ResourceProvider resourceProvider, LineInfo Function(String) getLineInfo,
+ {List<String> included = const <String>[],
+ int preferredPort,
+ bool enablePreview = true,
+ String summaryPath})
+ : super(listener, resourceProvider, getLineInfo,
+ included: included,
+ preferredPort: preferredPort,
+ enablePreview: enablePreview,
+ summaryPath: summaryPath);
+
+ @override
+ InstrumentationListener createInstrumentationListener(
+ {MigrationSummary migrationSummary}) =>
+ _ExceptionGeneratingInstrumentationListener(
+ migrationSummary: migrationSummary);
+}
+
class _MigrationCli extends MigrationCli {
+ /// If `true`, then an artifical exception should be generated when migration
+ /// encounters a reference to the `print` function.
+ final bool injectArtificialException;
+
Future<void> Function() _runWhilePreviewServerActive;
- _MigrationCli(_MigrationCliTestBase test)
+ _MigrationCli(_MigrationCliTestBase test,
+ {this.injectArtificialException = false})
: super(
binaryName: 'nnbd_migration',
loggerFactory: (isVerbose) => test.logger = _TestLogger(isVerbose),
@@ -50,6 +101,29 @@
_runWhilePreviewServerActive = null;
}
+ @override
+ NonNullableFix createNonNullableFix(DartFixListener listener,
+ ResourceProvider resourceProvider, LineInfo getLineInfo(String path),
+ {List<String> included = const <String>[],
+ int preferredPort,
+ bool enablePreview = true,
+ String summaryPath}) {
+ if (injectArtificialException) {
+ return _ExceptionGeneratingNonNullableFix(
+ listener, resourceProvider, getLineInfo,
+ included: included,
+ preferredPort: preferredPort,
+ enablePreview: enablePreview,
+ summaryPath: summaryPath);
+ } else {
+ return super.createNonNullableFix(listener, resourceProvider, getLineInfo,
+ included: included,
+ preferredPort: preferredPort,
+ enablePreview: enablePreview,
+ summaryPath: summaryPath);
+ }
+ }
+
Future<void> runWithPreviewServer(
ArgResults argResults, Future<void> callback()) async {
_runWhilePreviewServerActive = callback;
@@ -362,6 +436,16 @@
assertProjectContents(projectDir, simpleProject(migrated: true));
}
+ test_lifecycle_exception_handling() async {
+ var projectContents = simpleProject(sourceText: 'main() { print(0); }');
+ var projectDir = await createProjectDir(projectContents);
+ var cli = _createCli(injectArtificialException: true);
+ expect(
+ () async => runWithPreviewServer(cli, [projectDir], (url) async {}),
+ throwsA(TypeMatcher<Error>().having((e) => e.toString(), 'toString',
+ contains('Artificial exception triggered'))));
+ }
+
test_lifecycle_ignore_errors_disable() async {
var projectContents = simpleProject(sourceText: '''
int f() => null
@@ -1334,9 +1418,10 @@
headers: {'Content-Type': 'application/json; charset=UTF-8'});
}
- _MigrationCli _createCli() {
+ _MigrationCli _createCli({bool injectArtificialException = false}) {
mock_sdk.MockSdk(resourceProvider: resourceProvider);
- return _MigrationCli(this);
+ return _MigrationCli(this,
+ injectArtificialException: injectArtificialException);
}
Future<String> _getHelpText({@required bool verbose}) async {