send 'reloadRecommended' info to IDEs (#13996)

* send 'reloadRecommended' info to IDEs

* rename hint ==> hintMessage
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index 1996595..60eae6d 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -677,11 +677,14 @@
   });
 
 void stdoutCommandResponse(Map<String, dynamic> command) {
-  final String encoded = JSON.encode(command, toEncodable: _jsonEncodeObject);
-  stdout.writeln('[$encoded]');
+  stdout.writeln('[${jsonEncodeObject(command)}]');
 }
 
-dynamic _jsonEncodeObject(dynamic object) {
+String jsonEncodeObject(dynamic object) {
+  return JSON.encode(object, toEncodable: _toEncodable);
+}
+
+dynamic _toEncodable(dynamic object) {
   if (object is OperationResult)
     return _operationResultToMap(object);
   return object;
@@ -697,10 +700,17 @@
 }
 
 Map<String, dynamic> _operationResultToMap(OperationResult result) {
-  return <String, dynamic>{
+  final Map<String, dynamic> map = <String, dynamic>{
     'code': result.code,
-    'message': result.message
+    'message': result.message,
   };
+
+  if (result.hintMessage != null)
+    map['hintMessage'] = result.hintMessage;
+  if (result.hintId != null)
+    map['hintId'] = result.hintId;
+
+  return map;
 }
 
 dynamic _toJsonable(dynamic obj) {
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart
index b56e93f..770ee46 100644
--- a/packages/flutter_tools/lib/src/resident_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -843,11 +843,24 @@
 }
 
 class OperationResult {
-  OperationResult(this.code, this.message, { this.hint });
+  OperationResult(this.code, this.message, { this.hintMessage, this.hintId });
 
+  /// The result of the operation; a non-zero code indicates a failure.
   final int code;
+
+  /// A user facing message about the results of the operation.
   final String message;
-  final String hint;
+
+  /// An optional hint about the results of the operation. This is used to provide
+  /// sidecar data about the operation results. For example, this is used when
+  /// a reload is successful but some changed program elements where not run after a
+  /// reassemble.
+  final String hintMessage;
+
+  /// A key used by tools to discriminate between different kinds of operation results.
+  /// For example, a successful reload might have a [code] of 0 and a [hintId] of
+  /// `'restartRecommended'`.
+  final String hintId;
 
   bool get isOk => code == 0;
 
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart
index f8d227f..a4000ad 100644
--- a/packages/flutter_tools/lib/src/run_hot.dart
+++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -457,8 +457,8 @@
         status.cancel();
         if (result.isOk)
           printStatus('${result.message} in ${getElapsedAsMilliseconds(timer.elapsed)}.');
-        if (result.hint != null)
-          printStatus('\n${result.hint}');
+        if (result.hintMessage != null)
+          printStatus('\n${result.hintMessage}');
         return result;
       } catch (error) {
         status.cancel();
@@ -678,7 +678,8 @@
     return new OperationResult(
       reassembleAndScheduleErrors ? 1 : OperationResult.ok.code,
       reloadMessage,
-      hint: unusedElementMessage,
+      hintMessage: unusedElementMessage,
+      hintId: unusedElementMessage != null ? 'restartRecommended' : null,
     );
   }
 
diff --git a/packages/flutter_tools/test/commands/daemon_test.dart b/packages/flutter_tools/test/commands/daemon_test.dart
index 10a864b..07962c8 100644
--- a/packages/flutter_tools/test/commands/daemon_test.dart
+++ b/packages/flutter_tools/test/commands/daemon_test.dart
@@ -10,6 +10,7 @@
 import 'package:flutter_tools/src/commands/daemon.dart';
 import 'package:flutter_tools/src/globals.dart';
 import 'package:flutter_tools/src/ios/ios_workflow.dart';
+import 'package:flutter_tools/src/resident_runner.dart';
 import 'package:test/test.dart';
 
 import '../src/context.dart';
@@ -267,6 +268,23 @@
       IOSWorkflow: () => new MockIOSWorkflow(),
     });
   });
+
+  group('daemon serialization', () {
+    test('OperationResult', () {
+      expect(
+        jsonEncodeObject(OperationResult.ok),
+        '{"code":0,"message":""}'
+      );
+      expect(
+        jsonEncodeObject(new OperationResult(1, 'foo')),
+        '{"code":1,"message":"foo"}'
+      );
+      expect(
+        jsonEncodeObject(new OperationResult(0, 'foo', hintMessage: 'my hint', hintId: 'myId')),
+        '{"code":0,"message":"foo","hintMessage":"my hint","hintId":"myId"}'
+      );
+    });
+  });
 }
 
 bool _notEvent(Map<String, dynamic> map) => map['event'] == null;