Add a --preview-port flag to dart migrate.

Change-Id: I526a2e40198960e0e3fb3509ed8a84979f520b07
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/141765
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
diff --git a/pkg/analysis_server/lib/src/edit/fix/non_nullable_fix.dart b/pkg/analysis_server/lib/src/edit/fix/non_nullable_fix.dart
index 4d3b523..35c6e54 100644
--- a/pkg/analysis_server/lib/src/edit/fix/non_nullable_fix.dart
+++ b/pkg/analysis_server/lib/src/edit/fix/non_nullable_fix.dart
@@ -28,6 +28,8 @@
   /// mature enough.
   static const bool _usePermissiveMode = true;
 
+  final int preferredPort;
+
   final DartFixListener listener;
 
   /// The root of the included paths.
@@ -59,7 +61,8 @@
 
   Future<void> Function([List<String>]) rerunFunction;
 
-  NonNullableFix(this.listener, {List<String> included = const []})
+  NonNullableFix(this.listener,
+      {List<String> included = const [], this.preferredPort})
       : includedRoot =
             _getIncludedRoot(included, listener.server.resourceProvider) {
     reset();
@@ -70,6 +73,7 @@
 
   /// Return a list of the URLs corresponding to the included roots.
   List<String> get previewUrls => [
+        // TODO(jcollins-g): Change protocol to only return a single string.
         Uri(
             scheme: 'http',
             host: 'localhost',
@@ -85,7 +89,7 @@
     await state.refresh();
 
     if (server == null) {
-      server = HttpPreviewServer(state, rerun);
+      server = HttpPreviewServer(state, rerun, preferredPort);
       server.serveHttp();
       port = await server.boundPort;
       authToken = await server.authToken;
@@ -243,8 +247,8 @@
 
   static void task(DartFixRegistrar registrar, DartFixListener listener,
       EditDartfixParams params) {
-    registrar
-        .registerCodeTask(NonNullableFix(listener, included: params.included));
+    registrar.registerCodeTask(NonNullableFix(listener,
+        included: params.included, preferredPort: params.port));
   }
 
   /// Get the "root" of all [included] paths. See [includedRoot] for its
diff --git a/pkg/analysis_server/lib/src/edit/preview/http_preview_server.dart b/pkg/analysis_server/lib/src/edit/preview/http_preview_server.dart
index caf4d48..ccdcde1 100644
--- a/pkg/analysis_server/lib/src/edit/preview/http_preview_server.dart
+++ b/pkg/analysis_server/lib/src/edit/preview/http_preview_server.dart
@@ -35,8 +35,13 @@
   // A function which allows the migration to be rerun, taking changed paths.
   final Future<MigrationState> Function([List<String>]) rerunFunction;
 
+  /// Integer for a port to run the preview server on.  If null or zero, allow
+  /// [HttpServer.bind] to pick one.
+  final int preferredPort;
+
   /// Initialize a newly created HTTP server.
-  HttpPreviewServer(this.migrationState, this.rerunFunction);
+  HttpPreviewServer(
+      this.migrationState, this.rerunFunction, this.preferredPort);
 
   Future<String> get authToken async {
     await _serverFuture;
@@ -56,14 +61,14 @@
   }
 
   /// Begin serving HTTP requests over the given port.
-  Future<int> serveHttp([int initialPort]) async {
+  Future<int> serveHttp() async {
     if (_serverFuture != null) {
       return boundPort;
     }
 
     try {
       _serverFuture =
-          HttpServer.bind(InternetAddress.loopbackIPv4, initialPort ?? 0);
+          HttpServer.bind(InternetAddress.loopbackIPv4, preferredPort ?? 0);
 
       var server = await _serverFuture;
       _handleServer(server);
@@ -72,8 +77,8 @@
       // If we can't bind to the specified port, don't remember the broken
       // server.
       _serverFuture = null;
-
-      return null;
+      // TODO(jcollins-g): Display a better error message?
+      rethrow;
     }
   }
 
diff --git a/pkg/dartfix/lib/src/migrate/migrate.dart b/pkg/dartfix/lib/src/migrate/migrate.dart
index ceddcce..eb8357e0 100644
--- a/pkg/dartfix/lib/src/migrate/migrate.dart
+++ b/pkg/dartfix/lib/src/migrate/migrate.dart
@@ -200,6 +200,7 @@
       final EditDartfixParams params =
           EditDartfixParams([options.directoryAbsolute]);
       params.includedFixes = ['non-nullable'];
+      params.port = options.previewPort;
       json = await server.send(EDIT_REQUEST_DARTFIX, params.toJson());
       progress.finish(showTiming: true);
     } finally {
@@ -242,6 +243,8 @@
 
     if (options.webPreview) {
       String url = migrationResults.urls.first;
+      assert(migrationResults.urls.length <= 1,
+          'Got unexpected extra preview URLs from server');
 
       logger.stdout(ansi.emphasized('View migration results:'));
 
diff --git a/pkg/dartfix/lib/src/migrate/options.dart b/pkg/dartfix/lib/src/migrate/options.dart
index 20e2e67..f19908d 100644
--- a/pkg/dartfix/lib/src/migrate/options.dart
+++ b/pkg/dartfix/lib/src/migrate/options.dart
@@ -12,6 +12,7 @@
   static const applyChangesOption = 'apply-changes';
   static const debugOption = 'debug';
   static const ignoreErrorsOption = 'ignore-errors';
+  static const previewPortOption = 'preview-port';
   static const sdkPathOption = 'sdk-path';
   static const serverPathOption = 'server-path';
   static const webPreviewOption = 'web-preview';
@@ -20,6 +21,7 @@
   final bool debug;
   final String directory;
   final bool ignoreErrors;
+  final int previewPort;
   final String serverPath;
   final String sdkPath;
   final bool webPreview;
@@ -28,6 +30,8 @@
       : applyChanges = argResults[applyChangesOption] as bool,
         debug = argResults[debugOption] as bool,
         ignoreErrors = argResults[ignoreErrorsOption] as bool,
+        previewPort =
+            int.tryParse(argResults[previewPortOption] as String) ?? 0,
         sdkPath = argResults[sdkPathOption] as String,
         serverPath = argResults[serverPathOption] as String,
         webPreview = argResults['web-preview'] as bool;
@@ -67,6 +71,12 @@
       help: 'Override the SDK path used for migration.',
     );
     argParser.addOption(
+      previewPortOption,
+      defaultsTo: '0',
+      help: 'Run the preview server on the specified port.  If not specified '
+          'or invalid, dynamically allocate a port.',
+    );
+    argParser.addOption(
       serverPathOption,
       hide: true,
       help: 'Override the analysis server path used for migration.',