[analysis_server] Throw if ChangeBuilder.addDartFileEdit is used in a way that would lose edits

Change-Id: Ifee71a7b471512ba74bfbc4be5ce26d86a67da1a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/222720
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_core.dart b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_core.dart
index d2d24b8..ebb810a 100644
--- a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_core.dart
+++ b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_core.dart
@@ -130,6 +130,13 @@
     if (builder == null) {
       builder = await _createDartFileEditBuilder(path);
       if (builder != null) {
+        // It's not currently supported to call this method twice concurrently
+        // for the same file as two builder may be produced because of the above
+        // `await` so detect this and throw to avoid losing edits.
+        if (_dartFileEditBuilders.containsKey(path)) {
+          throw StateError(
+              "Can't add multiple edits concurrently for the same file");
+        }
         _dartFileEditBuilders[path] = builder;
       }
     }
diff --git a/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_dart_test.dart b/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_dart_test.dart
index 047e7a1..481147a 100644
--- a/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_dart_test.dart
+++ b/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_dart_test.dart
@@ -1868,6 +1868,42 @@
 ''');
   }
 
+  Future<void> test_multipleEdits_concurrently() async {
+    var initialCode = '00';
+    var path = convertPath('/home/test/lib/test.dart');
+    newFile(path, content: initialCode);
+
+    var builder = newBuilder();
+    var future = Future.wait([
+      builder.addDartFileEdit(path, (builder) {
+        builder.addSimpleInsertion(0, '11');
+      }),
+      builder.addDartFileEdit(path, (builder) {
+        builder.addSimpleInsertion(2, '22');
+      }),
+    ]);
+
+    expect(future, throwsA(TypeMatcher<StateError>()));
+  }
+
+  Future<void> test_multipleEdits_sequentially() async {
+    var initialCode = '00';
+    var path = convertPath('/home/test/lib/test.dart');
+    newFile(path, content: initialCode);
+
+    var builder = newBuilder();
+    await builder.addDartFileEdit(path, (builder) {
+      builder.addSimpleInsertion(0, '11');
+    });
+    await builder.addDartFileEdit(path, (builder) {
+      builder.addSimpleInsertion(2, '22');
+    });
+
+    var edits = getEdits(builder);
+    var resultCode = SourceEdit.applySequence(initialCode, edits);
+    expect(resultCode, '110022');
+  }
+
   Future<void> test_replaceTypeWithFuture() async {
     var path = convertPath('/home/test/lib/test.dart');
     addSource(path, 'String f() {}');