First cut dartfix protocol

and address comments in https://dart-review.googlesource.com/c/sdk/+/76320

Change-Id: I5c7ab40810d4116b1d36de90b7234d0a932bae82
Reviewed-on: https://dart-review.googlesource.com/76400
Commit-Queue: Dan Rubel <danrubel@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/doc/api.html b/pkg/analysis_server/doc/api.html
index 5cfd272..f9056d7 100644
--- a/pkg/analysis_server/doc/api.html
+++ b/pkg/analysis_server/doc/api.html
@@ -1851,6 +1851,7 @@
   
   
   
+  
 <h3>Requests</h3><dl><dt class="request"><a name="request_edit.format">edit.format</a></dt><dd><div class="box"><pre>request: {
   "id": String
   "method": "edit.format"
diff --git a/pkg/analysis_server/lib/protocol/protocol_constants.dart b/pkg/analysis_server/lib/protocol/protocol_constants.dart
index af80d5a..40d0ca6 100644
--- a/pkg/analysis_server/lib/protocol/protocol_constants.dart
+++ b/pkg/analysis_server/lib/protocol/protocol_constants.dart
@@ -125,6 +125,8 @@
 const String DIAGNOSTIC_REQUEST_GET_SERVER_PORT = 'diagnostic.getServerPort';
 const String DIAGNOSTIC_RESPONSE_GET_DIAGNOSTICS_CONTEXTS = 'contexts';
 const String DIAGNOSTIC_RESPONSE_GET_SERVER_PORT_PORT = 'port';
+const String EDIT_REQUEST_DARTFIX = 'edit.dartfix';
+const String EDIT_REQUEST_DARTFIX_INCLUDED = 'included';
 const String EDIT_REQUEST_FORMAT = 'edit.format';
 const String EDIT_REQUEST_FORMAT_FILE = 'file';
 const String EDIT_REQUEST_FORMAT_LINE_LENGTH = 'lineLength';
@@ -171,6 +173,8 @@
 const String EDIT_REQUEST_ORGANIZE_DIRECTIVES_FILE = 'file';
 const String EDIT_REQUEST_SORT_MEMBERS = 'edit.sortMembers';
 const String EDIT_REQUEST_SORT_MEMBERS_FILE = 'file';
+const String EDIT_RESPONSE_DARTFIX_DESCRIPTION = 'description';
+const String EDIT_RESPONSE_DARTFIX_FIXES = 'fixes';
 const String EDIT_RESPONSE_FORMAT_EDITS = 'edits';
 const String EDIT_RESPONSE_FORMAT_SELECTION_LENGTH = 'selectionLength';
 const String EDIT_RESPONSE_FORMAT_SELECTION_OFFSET = 'selectionOffset';
diff --git a/pkg/analysis_server/lib/protocol/protocol_generated.dart b/pkg/analysis_server/lib/protocol/protocol_generated.dart
index b1f5dce..aa83274 100644
--- a/pkg/analysis_server/lib/protocol/protocol_generated.dart
+++ b/pkg/analysis_server/lib/protocol/protocol_generated.dart
@@ -6166,6 +6166,218 @@
 }
 
 /**
+ * edit.dartfix params
+ *
+ * {
+ *   "included": List<FilePath>
+ * }
+ *
+ * Clients may not extend, implement or mix-in this class.
+ */
+class EditDartfixParams implements RequestParams {
+  List<String> _included;
+
+  /**
+   * A list of the files and directories for which edits should be suggested.
+   * If a request is made for a file which does not exist, or which is not
+   * currently subject to analysis (e.g. because it is not associated with any
+   * analysis root specified to analysis.setAnalysisRoots), an error of type
+   * FORMAT_INVALID_FILE will be generated.
+   */
+  List<String> get included => _included;
+
+  /**
+   * A list of the files and directories for which edits should be suggested.
+   * If a request is made for a file which does not exist, or which is not
+   * currently subject to analysis (e.g. because it is not associated with any
+   * analysis root specified to analysis.setAnalysisRoots), an error of type
+   * FORMAT_INVALID_FILE will be generated.
+   */
+  void set included(List<String> value) {
+    assert(value != null);
+    this._included = value;
+  }
+
+  EditDartfixParams(List<String> included) {
+    this.included = included;
+  }
+
+  factory EditDartfixParams.fromJson(
+      JsonDecoder jsonDecoder, String jsonPath, Object json) {
+    if (json == null) {
+      json = {};
+    }
+    if (json is Map) {
+      List<String> included;
+      if (json.containsKey("included")) {
+        included = jsonDecoder.decodeList(
+            jsonPath + ".included", json["included"], jsonDecoder.decodeString);
+      } else {
+        throw jsonDecoder.mismatch(jsonPath, "included");
+      }
+      return new EditDartfixParams(included);
+    } else {
+      throw jsonDecoder.mismatch(jsonPath, "edit.dartfix params", json);
+    }
+  }
+
+  factory EditDartfixParams.fromRequest(Request request) {
+    return new EditDartfixParams.fromJson(
+        new RequestDecoder(request), "params", request.params);
+  }
+
+  @override
+  Map<String, dynamic> toJson() {
+    Map<String, dynamic> result = {};
+    result["included"] = included;
+    return result;
+  }
+
+  @override
+  Request toRequest(String id) {
+    return new Request(id, "edit.dartfix", toJson());
+  }
+
+  @override
+  String toString() => json.encode(toJson());
+
+  @override
+  bool operator ==(other) {
+    if (other is EditDartfixParams) {
+      return listEqual(
+          included, other.included, (String a, String b) => a == b);
+    }
+    return false;
+  }
+
+  @override
+  int get hashCode {
+    int hash = 0;
+    hash = JenkinsSmiHash.combine(hash, included.hashCode);
+    return JenkinsSmiHash.finish(hash);
+  }
+}
+
+/**
+ * edit.dartfix result
+ *
+ * {
+ *   "description": List<String>
+ *   "fixes": List<SourceFileEdit>
+ * }
+ *
+ * Clients may not extend, implement or mix-in this class.
+ */
+class EditDartfixResult implements ResponseResult {
+  List<String> _description;
+
+  List<SourceFileEdit> _fixes;
+
+  /**
+   * A list of human readable changes made by applying the fixes.
+   */
+  List<String> get description => _description;
+
+  /**
+   * A list of human readable changes made by applying the fixes.
+   */
+  void set description(List<String> value) {
+    assert(value != null);
+    this._description = value;
+  }
+
+  /**
+   * The suggested fixes.
+   */
+  List<SourceFileEdit> get fixes => _fixes;
+
+  /**
+   * The suggested fixes.
+   */
+  void set fixes(List<SourceFileEdit> value) {
+    assert(value != null);
+    this._fixes = value;
+  }
+
+  EditDartfixResult(List<String> description, List<SourceFileEdit> fixes) {
+    this.description = description;
+    this.fixes = fixes;
+  }
+
+  factory EditDartfixResult.fromJson(
+      JsonDecoder jsonDecoder, String jsonPath, Object json) {
+    if (json == null) {
+      json = {};
+    }
+    if (json is Map) {
+      List<String> description;
+      if (json.containsKey("description")) {
+        description = jsonDecoder.decodeList(jsonPath + ".description",
+            json["description"], jsonDecoder.decodeString);
+      } else {
+        throw jsonDecoder.mismatch(jsonPath, "description");
+      }
+      List<SourceFileEdit> fixes;
+      if (json.containsKey("fixes")) {
+        fixes = jsonDecoder.decodeList(
+            jsonPath + ".fixes",
+            json["fixes"],
+            (String jsonPath, Object json) =>
+                new SourceFileEdit.fromJson(jsonDecoder, jsonPath, json));
+      } else {
+        throw jsonDecoder.mismatch(jsonPath, "fixes");
+      }
+      return new EditDartfixResult(description, fixes);
+    } else {
+      throw jsonDecoder.mismatch(jsonPath, "edit.dartfix result", json);
+    }
+  }
+
+  factory EditDartfixResult.fromResponse(Response response) {
+    return new EditDartfixResult.fromJson(
+        new ResponseDecoder(REQUEST_ID_REFACTORING_KINDS.remove(response.id)),
+        "result",
+        response.result);
+  }
+
+  @override
+  Map<String, dynamic> toJson() {
+    Map<String, dynamic> result = {};
+    result["description"] = description;
+    result["fixes"] =
+        fixes.map((SourceFileEdit value) => value.toJson()).toList();
+    return result;
+  }
+
+  @override
+  Response toResponse(String id) {
+    return new Response(id, result: toJson());
+  }
+
+  @override
+  String toString() => json.encode(toJson());
+
+  @override
+  bool operator ==(other) {
+    if (other is EditDartfixResult) {
+      return listEqual(
+              description, other.description, (String a, String b) => a == b) &&
+          listEqual(fixes, other.fixes,
+              (SourceFileEdit a, SourceFileEdit b) => a == b);
+    }
+    return false;
+  }
+
+  @override
+  int get hashCode {
+    int hash = 0;
+    hash = JenkinsSmiHash.combine(hash, description.hashCode);
+    hash = JenkinsSmiHash.combine(hash, fixes.hashCode);
+    return JenkinsSmiHash.finish(hash);
+  }
+}
+
+/**
  * edit.format params
  *
  * {
diff --git a/pkg/analysis_server/test/integration/coverage.md b/pkg/analysis_server/test/integration/coverage.md
index 2dbaa43..dcec50a 100644
--- a/pkg/analysis_server/test/integration/coverage.md
+++ b/pkg/analysis_server/test/integration/coverage.md
@@ -38,6 +38,7 @@
 - [x] diagnostic.getServerPort
 
 ## edit domain
+- [ ] edit.dartfix
 - [x] edit.format
 - [x] edit.getAssists
 - [x] edit.getAvailableRefactorings
diff --git a/pkg/analysis_server/test/integration/support/integration_test_methods.dart b/pkg/analysis_server/test/integration/support/integration_test_methods.dart
index 3433f34..0d95b2b 100644
--- a/pkg/analysis_server/test/integration/support/integration_test_methods.dart
+++ b/pkg/analysis_server/test/integration/support/integration_test_methods.dart
@@ -1495,6 +1495,39 @@
   }
 
   /**
+   * Analyze the specified sources for recommended changes and return a set of
+   * suggested edits for those sources. These edits may include changes to
+   * sources outside the set of specified sources if a change in a specified
+   * source requires it.
+   *
+   * Parameters
+   *
+   * included: List<FilePath>
+   *
+   *   A list of the files and directories for which edits should be suggested.
+   *   If a request is made for a file which does not exist, or which is not
+   *   currently subject to analysis (e.g. because it is not associated with
+   *   any analysis root specified to analysis.setAnalysisRoots), an error of
+   *   type FORMAT_INVALID_FILE will be generated.
+   *
+   * Returns
+   *
+   * description: List<String>
+   *
+   *   A list of human readable changes made by applying the fixes.
+   *
+   * fixes: List<SourceFileEdit>
+   *
+   *   The suggested fixes.
+   */
+  Future<EditDartfixResult> sendEditDartfix(List<String> included) async {
+    var params = new EditDartfixParams(included).toJson();
+    var result = await server.send("edit.dartfix", params);
+    ResponseDecoder decoder = new ResponseDecoder(null);
+    return new EditDartfixResult.fromJson(decoder, 'result', result);
+  }
+
+  /**
    * Return the set of fixes that are available for the errors at a given
    * offset in a given file.
    *
diff --git a/pkg/analysis_server/test/integration/support/protocol_matchers.dart b/pkg/analysis_server/test/integration/support/protocol_matchers.dart
index 11b741a..26ecc39 100644
--- a/pkg/analysis_server/test/integration/support/protocol_matchers.dart
+++ b/pkg/analysis_server/test/integration/support/protocol_matchers.dart
@@ -2122,6 +2122,28 @@
     new MatchesJsonObject("diagnostic.getServerPort result", {"port": isInt}));
 
 /**
+ * edit.dartfix params
+ *
+ * {
+ *   "included": List<FilePath>
+ * }
+ */
+final Matcher isEditDartfixParams = new LazyMatcher(() => new MatchesJsonObject(
+    "edit.dartfix params", {"included": isListOf(isFilePath)}));
+
+/**
+ * edit.dartfix result
+ *
+ * {
+ *   "description": List<String>
+ *   "fixes": List<SourceFileEdit>
+ * }
+ */
+final Matcher isEditDartfixResult = new LazyMatcher(() => new MatchesJsonObject(
+    "edit.dartfix result",
+    {"description": isListOf(isString), "fixes": isListOf(isSourceFileEdit)}));
+
+/**
  * edit.format params
  *
  * {
diff --git a/pkg/analysis_server/tool/spec/generated/java/AnalysisServer.java b/pkg/analysis_server/tool/spec/generated/java/AnalysisServer.java
index 8a410b7..551d5dd 100644
--- a/pkg/analysis_server/tool/spec/generated/java/AnalysisServer.java
+++ b/pkg/analysis_server/tool/spec/generated/java/AnalysisServer.java
@@ -406,6 +406,20 @@
   public void diagnostic_getServerPort(GetServerPortConsumer consumer);
 
   /**
+   * {@code edit.dartfix}
+   *
+   * Analyze the specified sources for recommended changes and return a set of suggested edits for
+   * those sources. These edits may include changes to sources outside the set of specified sources
+   * if a change in a specified source requires it.
+   *
+   * @param included A list of the files and directories for which edits should be suggested. If a
+   *         request is made for a file which does not exist, or which is not currently subject to
+   *         analysis (e.g. because it is not associated with any analysis root specified to
+   *         analysis.setAnalysisRoots), an error of type FORMAT_INVALID_FILE will be generated.
+   */
+  public void edit_dartfix(List<String> included, DartfixConsumer consumer);
+
+  /**
    * {@code edit.format}
    *
    * Format the contents of a single file. The currently selected region of text is passed in so that
diff --git a/pkg/analysis_server/tool/spec/spec_input.html b/pkg/analysis_server/tool/spec/spec_input.html
index 2334b39..d5b0623 100644
--- a/pkg/analysis_server/tool/spec/spec_input.html
+++ b/pkg/analysis_server/tool/spec/spec_input.html
@@ -1933,6 +1933,46 @@
       </field>
     </result>
   </request>
+  <request method="dartfix" experimental="true">
+    <p>
+      Analyze the specified sources for recommended changes
+      and return a set of suggested edits for those sources.
+      These edits may include changes to sources outside the set
+      of specified sources if a change in a specified source requires it.
+    </p>
+    <params>
+      <field name="included">
+        <list>
+          <ref>FilePath</ref>
+        </list>
+        <p>
+          A list of the files and directories for which edits should be suggested.
+          If a request is made for a file which does not exist, or which is not
+          currently subject to analysis (e.g. because it is not associated with
+          any analysis root specified to analysis.setAnalysisRoots), an error of
+          type <tt>FORMAT_INVALID_FILE</tt> will be generated.
+        </p>
+      </field>
+    </params>
+    <result>
+      <field name="description">
+        <list>
+          <ref>String</ref>
+        </list>
+        <p>
+          A list of human readable changes made by applying the fixes.
+        </p>
+      </field>
+      <field name="fixes">
+        <list>
+          <ref>SourceFileEdit</ref>
+        </list>
+        <p>
+          The suggested fixes.
+        </p>
+      </field>
+    </result>
+  </request>
   <request method="getFixes">
     <p>
       Return the set of fixes that are available for the errors at
diff --git a/pkg/analyzer_cli/lib/src/fix/driver.dart b/pkg/analyzer_cli/lib/src/fix/driver.dart
index 5bbf033..5888e26 100644
--- a/pkg/analyzer_cli/lib/src/fix/driver.dart
+++ b/pkg/analyzer_cli/lib/src/fix/driver.dart
@@ -1,3 +1,6 @@
+// Copyright (c) 2018, 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 'dart:async';
 
 import 'package:analyzer_cli/src/fix/options.dart';
diff --git a/pkg/analyzer_cli/lib/src/fix/options.dart b/pkg/analyzer_cli/lib/src/fix/options.dart
index 802eac1..0cb29c0 100644
--- a/pkg/analyzer_cli/lib/src/fix/options.dart
+++ b/pkg/analyzer_cli/lib/src/fix/options.dart
@@ -1,3 +1,6 @@
+// Copyright (c) 2018, 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 'dart:io';
 
 import 'package:analyzer/src/util/sdk.dart';
diff --git a/pkg/analyzer_cli/lib/src/fix/server.dart b/pkg/analyzer_cli/lib/src/fix/server.dart
index 31467ab..66f031c 100644
--- a/pkg/analyzer_cli/lib/src/fix/server.dart
+++ b/pkg/analyzer_cli/lib/src/fix/server.dart
@@ -1,3 +1,6 @@
+// Copyright (c) 2018, 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 'dart:async';
 import 'dart:convert';
 import 'dart:io';