Version 2.17.0-164.0.dev

Merge commit 'f5bd632abe415e32784e619971d84c9562ed0fd3' into 'dev'
diff --git a/pkg/analysis_server/lib/src/lsp/mapping.dart b/pkg/analysis_server/lib/src/lsp/mapping.dart
index fec47e8..9026e05 100644
--- a/pkg/analysis_server/lib/src/lsp/mapping.dart
+++ b/pkg/analysis_server/lib/src/lsp/mapping.dart
@@ -983,10 +983,12 @@
   );
 
   // For LSP, we need to provide the main edit and other edits separately. The
-  // main edit must include the location that completion was invoked. If we
-  // fail to find exactly one, this is an error.
+  // main edit must include the location that completion was invoked. If we find
+  // more than one, take the first one since imports are usually added as later
+  // edits (so when applied sequentially they will be inserted at the start of
+  // the file after the other edits).
   final mainEdit = mainFileEdits
-      .singleWhere((edit) => edit.range.start.line == position.line);
+      .firstWhere((edit) => edit.range.start.line == position.line);
   final nonMainEdits = mainFileEdits.where((edit) => edit != mainEdit).toList();
 
   return lsp.CompletionItem(
@@ -1481,12 +1483,16 @@
 }) {
   final snippetEdits = <lsp.SnippetTextEdit>[];
 
-  // Edit groups offsets are based on after the edits are applied, so we
-  // must track the offset delta to ensure we track the offset within the
-  // edits. This also requires the edits are sorted earliest-to-latest.
+  // Edit groups offsets are based on the document after the edits are applied.
+  // This means we must compute an offset delta for each edit that takes into
+  // account all edits that might be made before it in the document (which are
+  // after it in the edits). To do this, reverse the list when computing the
+  // offsets, but reverse them back to the original list order when returning so
+  // that we do not apply them incorrectly in tests (where we will apply them
+  // in-sequence).
+
   var offsetDelta = 0;
-  change.edits.sortBy<num>((edit) => edit.offset);
-  for (final edit in change.edits) {
+  for (final edit in change.edits.reversed) {
     snippetEdits.add(snippetTextEditFromEditGroups(
       filePath,
       lineInfo,
@@ -1499,7 +1505,7 @@
     offsetDelta += edit.replacement.length - edit.length;
   }
 
-  return snippetEdits;
+  return snippetEdits.reversed.toList();
 }
 
 ErrorOr<server.SourceRange> toSourceRange(
diff --git a/pkg/analysis_server/test/lsp/completion_dart_test.dart b/pkg/analysis_server/test/lsp/completion_dart_test.dart
index 0db91bd..1b9520b 100644
--- a/pkg/analysis_server/test/lsp/completion_dart_test.dart
+++ b/pkg/analysis_server/test/lsp/completion_dart_test.dart
@@ -2607,6 +2607,60 @@
 ''');
   }
 
+  Future<void> test_snippets_flutterStateless_addsImports_onlyPrefix() async {
+    final content = '''
+stless^
+''';
+
+    await initializeWithSnippetSupportAndPreviewFlag();
+    final updated = await expectAndApplySnippet(
+      content,
+      prefix: FlutterStatelessWidgetSnippetProducer.prefix,
+      label: FlutterStatelessWidgetSnippetProducer.label,
+    );
+
+    expect(updated, '''
+import 'package:flutter/src/foundation/key.dart';
+import 'package:flutter/src/widgets/framework.dart';
+
+class \${1:MyWidget} extends StatelessWidget {
+  const \${1:MyWidget}({Key$questionSuffix key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    \$0
+  }
+}
+''');
+  }
+
+  Future<void> test_snippets_flutterStateless_addsImports_zeroOffset() async {
+    final content = '''
+^
+'''; // Deliberate trailing newline to ensure imports aren't inserted at "end".
+
+    await initializeWithSnippetSupportAndPreviewFlag();
+    final updated = await expectAndApplySnippet(
+      content,
+      prefix: FlutterStatelessWidgetSnippetProducer.prefix,
+      label: FlutterStatelessWidgetSnippetProducer.label,
+    );
+
+    expect(updated, '''
+import 'package:flutter/src/foundation/key.dart';
+import 'package:flutter/src/widgets/framework.dart';
+
+class \${1:MyWidget} extends StatelessWidget {
+  const \${1:MyWidget}({Key$questionSuffix key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    \$0
+  }
+}
+''');
+  }
+
   Future<void> test_snippets_flutterStateless_notAvailable_notTopLevel() async {
     final content = '''
 class A {
diff --git a/pkg/analysis_server/test/services/snippets/dart/flutter_snippet_producers_test.dart b/pkg/analysis_server/test/services/snippets/dart/flutter_snippet_producers_test.dart
index ce15a90..6edf13b 100644
--- a/pkg/analysis_server/test/services/snippets/dart/flutter_snippet_producers_test.dart
+++ b/pkg/analysis_server/test/services/snippets/dart/flutter_snippet_producers_test.dart
@@ -2,6 +2,7 @@
 // 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 'package:analysis_server/src/protocol_server.dart';
 import 'package:analysis_server/src/services/snippets/dart/flutter_snippet_producers.dart';
 import 'package:analysis_server/src/services/snippets/dart/snippet_manager.dart';
 import 'package:test/test.dart';
@@ -20,8 +21,14 @@
 }
 
 abstract class FlutterSnippetProducerTest extends AbstractSingleUnitTest {
+  SnippetProducerGenerator get generator;
+  String get label;
+  String get prefix;
+
+  @override
+  bool get verifyNoTestUnitErrors => false;
+
   Future<void> expectNotValidSnippet(
-    SnippetProducerGenerator generator,
     String code,
   ) async {
     await resolveTestCode(withoutMarkers(code));
@@ -34,8 +41,7 @@
     expect(await producer.isValid(), isFalse);
   }
 
-  Future<Snippet> expectValidSnippet(
-      SnippetProducerGenerator generator, String code) async {
+  Future<Snippet> expectValidSnippet(String code) async {
     await resolveTestCode(withoutMarkers(code));
     final request = DartSnippetRequest(
       unit: testAnalysisResult,
@@ -46,221 +52,251 @@
     expect(await producer.isValid(), isTrue);
     return producer.compute();
   }
+
+  /// Checks snippets can produce edits where the imports and snippet will be
+  /// inserted at the same location.
+  ///
+  /// For example, when a document is completely empty besides the snippet
+  /// prefix, the imports will be inserted at offset 0 and the snippet will
+  /// replace from 0 to the end of the typed prefix.
+  Future<void> test_valid_importsAndEditsOverlap() async {
+    writeTestPackageConfig(flutter: true);
+
+    final snippet = await expectValidSnippet('$prefix^');
+    expect(snippet.prefix, prefix);
+
+    // Main edits replace $prefix.length characters starting at $prefix
+    final mainEdit = snippet.change.edits[0].edits[0];
+    expect(mainEdit.offset, testCode.indexOf(prefix));
+    expect(mainEdit.length, prefix.length);
+
+    // Imports inserted at start of doc (0)
+    final importEdit = snippet.change.edits[0].edits[1];
+    expect(importEdit.offset, 0);
+    expect(importEdit.length, 0);
+  }
+
+  Future<void> test_valid_suffixReplacement() async {
+    writeTestPackageConfig(flutter: true);
+
+    final snippet = await expectValidSnippet('''
+class A {}
+
+$prefix^
+''');
+    expect(snippet.prefix, prefix);
+
+    // Main edits replace $prefix.length characters starting at $prefix
+    final mainEdit = snippet.change.edits[0].edits[0];
+    expect(mainEdit.offset, testCode.indexOf(prefix));
+    expect(mainEdit.length, prefix.length);
+
+    // Imports inserted at start of doc (0)
+    final importEdit = snippet.change.edits[0].edits[1];
+    expect(importEdit.offset, 0);
+    expect(importEdit.length, 0);
+  }
 }
 
 @reflectiveTest
 class FlutterStatefulWidgetSnippetProducerTest
     extends FlutterSnippetProducerTest {
+  @override
   final generator = FlutterStatefulWidgetSnippetProducer.newInstance;
+
+  @override
+  String get label => FlutterStatefulWidgetSnippetProducer.label;
+
+  @override
+  String get prefix => FlutterStatefulWidgetSnippetProducer.prefix;
+
   Future<void> test_notValid_notFlutterProject() async {
     writeTestPackageConfig();
 
-    await expectNotValidSnippet(generator, '^');
+    await expectNotValidSnippet('^');
   }
 
   Future<void> test_valid() async {
     writeTestPackageConfig(flutter: true);
 
-    final snippet = await expectValidSnippet(generator, '^');
+    final snippet = await expectValidSnippet('^');
     expect(snippet.prefix, 'stful');
     expect(snippet.label, 'Flutter Stateful Widget');
-    expect(snippet.change.toJson(), {
-      'message': '',
-      'edits': [
-        {
-          'file': testFile,
-          'fileStamp': 0,
-          'edits': [
-            {
-              'offset': 0,
-              'length': 0,
-              'replacement':
-                  'import \'package:flutter/src/foundation/key.dart\';\n'
-                      'import \'package:flutter/src/widgets/framework.dart\';\n'
-            },
-            {
-              'offset': 0,
-              'length': 0,
-              'replacement': 'class MyWidget extends StatefulWidget {\n'
-                  '  const MyWidget({Key? key}) : super(key: key);\n'
-                  '\n'
-                  '  @override\n'
-                  '  State<MyWidget> createState() => _MyWidgetState();\n'
-                  '}\n'
-                  '\n'
-                  'class _MyWidgetState extends State<MyWidget> {\n'
-                  '  @override\n'
-                  '  Widget build(BuildContext context) {\n'
-                  '    \n'
-                  '  }\n'
-                  '}'
-            }
-          ]
-        }
-      ],
-      'linkedEditGroups': [
-        {
-          'positions': [
-            {'file': testFile, 'offset': 109},
-            {'file': testFile, 'offset': 151},
-            {'file': testFile, 'offset': 212},
-            {'file': testFile, 'offset': 240},
-            {'file': testFile, 'offset': 267},
-            {'file': testFile, 'offset': 295}
-          ],
-          'length': 8,
-          'suggestions': []
-        }
-      ],
-      'selection': {'file': testFile, 'offset': 362}
-    });
+    var code = '';
+    expect(snippet.change.edits, hasLength(1));
+    snippet.change.edits
+        .forEach((edit) => code = SourceEdit.applySequence(code, edit.edits));
+    expect(code, '''
+import 'package:flutter/src/foundation/key.dart';
+import 'package:flutter/src/widgets/framework.dart';
+
+class MyWidget extends StatefulWidget {
+  const MyWidget({Key? key}) : super(key: key);
+
+  @override
+  State<MyWidget> createState() => _MyWidgetState();
+}
+
+class _MyWidgetState extends State<MyWidget> {
+  @override
+  Widget build(BuildContext context) {
+    
+  }
+}''');
+    expect(snippet.change.selection!.file, testFile);
+    expect(snippet.change.selection!.offset, 363);
+    expect(snippet.change.linkedEditGroups.map((group) => group.toJson()), [
+      {
+        'positions': [
+          {'file': testFile, 'offset': 110},
+          {'file': testFile, 'offset': 152},
+          {'file': testFile, 'offset': 213},
+          {'file': testFile, 'offset': 241},
+          {'file': testFile, 'offset': 268},
+          {'file': testFile, 'offset': 296},
+        ],
+        'length': 8,
+        'suggestions': []
+      }
+    ]);
   }
 }
 
 @reflectiveTest
 class FlutterStatefulWidgetWithAnimationControllerSnippetProducerTest
     extends FlutterSnippetProducerTest {
+  @override
   final generator =
       FlutterStatefulWidgetWithAnimationControllerSnippetProducer.newInstance;
+
+  @override
+  String get label =>
+      FlutterStatefulWidgetWithAnimationControllerSnippetProducer.label;
+
+  @override
+  String get prefix =>
+      FlutterStatefulWidgetWithAnimationControllerSnippetProducer.prefix;
+
   Future<void> test_notValid_notFlutterProject() async {
     writeTestPackageConfig();
 
-    await expectNotValidSnippet(generator, '^');
+    await expectNotValidSnippet('^');
   }
 
   Future<void> test_valid() async {
     writeTestPackageConfig(flutter: true);
 
-    final snippet = await expectValidSnippet(generator, '^');
+    final snippet = await expectValidSnippet('^');
     expect(snippet.prefix, 'stanim');
     expect(snippet.label, 'Flutter Widget with AnimationController');
-    expect(snippet.change.toJson(), {
-      'message': '',
-      'edits': [
-        {
-          'file': testFile,
-          'fileStamp': 0,
-          'edits': [
-            {
-              'offset': 0,
-              'length': 0,
-              'replacement':
-                  'import \'package:flutter/src/animation/animation_controller.dart\';\n'
-                      'import \'package:flutter/src/foundation/key.dart\';\n'
-                      'import \'package:flutter/src/widgets/framework.dart\';\n'
-                      'import \'package:flutter/src/widgets/ticker_provider.dart\';\n'
-            },
-            {
-              'offset': 0,
-              'length': 0,
-              'replacement': 'class MyWidget extends StatefulWidget {\n'
-                  '  const MyWidget({Key? key}) : super(key: key);\n'
-                  '\n'
-                  '  @override\n'
-                  '  State<MyWidget> createState() => _MyWidgetState();\n'
-                  '}\n'
-                  '\n'
-                  'class _MyWidgetState extends State<MyWidget>\n'
-                  '    with SingleTickerProviderStateMixin {\n'
-                  '  late AnimationController _controller;\n'
-                  '\n'
-                  '  @override\n'
-                  '  void initState() {\n'
-                  '    super.initState();\n'
-                  '    _controller = AnimationController(vsync: this);\n'
-                  '  }\n'
-                  '\n'
-                  '  @override\n'
-                  '  void dispose() {\n'
-                  '    super.dispose();\n'
-                  '    _controller.dispose();\n'
-                  '  }\n'
-                  '\n'
-                  '  @override\n'
-                  '  Widget build(BuildContext context) {\n'
-                  '    \n'
-                  '  }\n'
-                  '}'
-            }
-          ]
-        }
-      ],
-      'linkedEditGroups': [
-        {
-          'positions': [
-            {'file': testFile, 'offset': 234},
-            {'file': testFile, 'offset': 276},
-            {'file': testFile, 'offset': 337},
-            {'file': testFile, 'offset': 365},
-            {'file': testFile, 'offset': 392},
-            {'file': testFile, 'offset': 420}
-          ],
-          'length': 8,
-          'suggestions': []
-        }
-      ],
-      'selection': {'file': testFile, 'offset': 765}
-    });
+    var code = '';
+    expect(snippet.change.edits, hasLength(1));
+    snippet.change.edits
+        .forEach((edit) => code = SourceEdit.applySequence(code, edit.edits));
+    expect(code, '''
+import 'package:flutter/src/animation/animation_controller.dart';
+import 'package:flutter/src/foundation/key.dart';
+import 'package:flutter/src/widgets/framework.dart';
+import 'package:flutter/src/widgets/ticker_provider.dart';
+
+class MyWidget extends StatefulWidget {
+  const MyWidget({Key? key}) : super(key: key);
+
+  @override
+  State<MyWidget> createState() => _MyWidgetState();
+}
+
+class _MyWidgetState extends State<MyWidget>
+    with SingleTickerProviderStateMixin {
+  late AnimationController _controller;
+
+  @override
+  void initState() {
+    super.initState();
+    _controller = AnimationController(vsync: this);
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+    _controller.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    
+  }
+}''');
+    expect(snippet.change.selection!.file, testFile);
+    expect(snippet.change.selection!.offset, 766);
+    expect(snippet.change.linkedEditGroups.map((group) => group.toJson()), [
+      {
+        'positions': [
+          {'file': testFile, 'offset': 235},
+          {'file': testFile, 'offset': 277},
+          {'file': testFile, 'offset': 338},
+          {'file': testFile, 'offset': 366},
+          {'file': testFile, 'offset': 393},
+          {'file': testFile, 'offset': 421},
+        ],
+        'length': 8,
+        'suggestions': []
+      }
+    ]);
   }
 }
 
 @reflectiveTest
 class FlutterStatelessWidgetSnippetProducerTest
     extends FlutterSnippetProducerTest {
+  @override
   final generator = FlutterStatelessWidgetSnippetProducer.newInstance;
 
+  @override
+  String get label => FlutterStatelessWidgetSnippetProducer.label;
+
+  @override
+  String get prefix => FlutterStatelessWidgetSnippetProducer.prefix;
+
   Future<void> test_notValid_notFlutterProject() async {
     writeTestPackageConfig();
 
-    await expectNotValidSnippet(generator, '^');
+    await expectNotValidSnippet('^');
   }
 
   Future<void> test_valid() async {
     writeTestPackageConfig(flutter: true);
 
-    final snippet = await expectValidSnippet(generator, '^');
+    final snippet = await expectValidSnippet('^');
     expect(snippet.prefix, 'stless');
     expect(snippet.label, 'Flutter Stateless Widget');
-    expect(snippet.change.toJson(), {
-      'message': '',
-      'edits': [
-        {
-          'file': testFile,
-          'fileStamp': 0,
-          'edits': [
-            {
-              'offset': 0,
-              'length': 0,
-              'replacement':
-                  'import \'package:flutter/src/foundation/key.dart\';\n'
-                      'import \'package:flutter/src/widgets/framework.dart\';\n'
-            },
-            {
-              'offset': 0,
-              'length': 0,
-              'replacement': 'class MyWidget extends StatelessWidget {\n'
-                  '  const MyWidget({Key? key}) : super(key: key);\n'
-                  '\n'
-                  '  @override\n'
-                  '  Widget build(BuildContext context) {\n'
-                  '    \n'
-                  '  }\n'
-                  '}'
-            }
-          ]
-        }
-      ],
-      'linkedEditGroups': [
-        {
-          'positions': [
-            {'file': testFile, 'offset': 109},
-            {'file': testFile, 'offset': 152}
-          ],
-          'length': 8,
-          'suggestions': []
-        }
-      ],
-      'selection': {'file': testFile, 'offset': 248}
-    });
+    var code = '';
+    expect(snippet.change.edits, hasLength(1));
+    snippet.change.edits
+        .forEach((edit) => code = SourceEdit.applySequence(code, edit.edits));
+    expect(code, '''
+import 'package:flutter/src/foundation/key.dart';
+import 'package:flutter/src/widgets/framework.dart';
+
+class MyWidget extends StatelessWidget {
+  const MyWidget({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    
+  }
+}''');
+    expect(snippet.change.selection!.file, testFile);
+    expect(snippet.change.selection!.offset, 249);
+    expect(snippet.change.linkedEditGroups.map((group) => group.toJson()), [
+      {
+        'positions': [
+          {'file': testFile, 'offset': 110},
+          {'file': testFile, 'offset': 153},
+        ],
+        'length': 8,
+        'suggestions': []
+      }
+    ]);
   }
 }
diff --git a/pkg/analysis_server_client/lib/src/protocol/protocol_common.dart b/pkg/analysis_server_client/lib/src/protocol/protocol_common.dart
index ca03c4d..97e7427 100644
--- a/pkg/analysis_server_client/lib/src/protocol/protocol_common.dart
+++ b/pkg/analysis_server_client/lib/src/protocol/protocol_common.dart
@@ -4373,8 +4373,14 @@
   }
 
   /// Adds [edit] to the [FileEdit] for the given [file].
-  void addEdit(String file, int fileStamp, SourceEdit edit) =>
-      addEditToSourceChange(this, file, fileStamp, edit);
+  ///
+  /// If [insertBeforeExisting] is `true`, inserts made at the same offset as
+  /// other edits will be inserted such that they appear before them in the
+  /// resulting document.
+  void addEdit(String file, int fileStamp, SourceEdit edit,
+          {bool insertBeforeExisting = false}) =>
+      addEditToSourceChange(this, file, fileStamp, edit,
+          insertBeforeExisting: insertBeforeExisting);
 
   /// Adds the given [FileEdit].
   void addFileEdit(SourceFileEdit edit) {
@@ -4597,10 +4603,22 @@
   }
 
   /// Adds the given [Edit] to the list.
-  void add(SourceEdit edit) => addEditForSource(this, edit);
+  ///
+  /// If [insertBeforeExisting] is `true`, inserts made at the same offset as
+  /// other edits will be inserted such that they appear before them in the
+  /// resulting document.
+  void add(SourceEdit edit, {bool insertBeforeExisting = false}) =>
+      addEditForSource(this, edit, insertBeforeExisting: insertBeforeExisting);
 
   /// Adds the given [Edit]s.
-  void addAll(Iterable<SourceEdit> edits) => addAllEditsForSource(this, edits);
+  ///
+  /// If [insertBeforeExisting] is `true`, inserts made at the same offset as
+  /// other edits will be inserted such that they appear before them in the
+  /// resulting document.
+  void addAll(Iterable<SourceEdit> edits,
+          {bool insertBeforeExisting = false}) =>
+      addAllEditsForSource(this, edits,
+          insertBeforeExisting: insertBeforeExisting);
 
   @override
   String toString() => json.encode(toJson());
diff --git a/pkg/analysis_server_client/lib/src/protocol/protocol_internal.dart b/pkg/analysis_server_client/lib/src/protocol/protocol_internal.dart
index 399fb51..431cca2 100644
--- a/pkg/analysis_server_client/lib/src/protocol/protocol_internal.dart
+++ b/pkg/analysis_server_client/lib/src/protocol/protocol_internal.dart
@@ -13,30 +13,54 @@
     HashMap<String, RefactoringKind>();
 
 /// Adds the given [sourceEdits] to the list in [sourceFileEdit].
+///
+/// If [insertBeforeExisting] is `true`, inserts made at the same offset as
+/// other edits will be inserted such that they appear before them in the
+/// resulting document.
 void addAllEditsForSource(
-    SourceFileEdit sourceFileEdit, Iterable<SourceEdit> edits) {
-  edits.forEach(sourceFileEdit.add);
+    SourceFileEdit sourceFileEdit, Iterable<SourceEdit> edits,
+    {bool insertBeforeExisting = false}) {
+  edits.forEach((edit) =>
+      sourceFileEdit.add(edit, insertBeforeExisting: insertBeforeExisting));
 }
 
 /// Adds the given [sourceEdit] to the list in [sourceFileEdit].
-void addEditForSource(SourceFileEdit sourceFileEdit, SourceEdit sourceEdit) {
+///
+/// If [insertBeforeExisting] is `true`, inserts made at the same offset as
+/// other edits will be inserted such that they appear before them in the
+/// resulting document.
+void addEditForSource(SourceFileEdit sourceFileEdit, SourceEdit sourceEdit,
+    {bool insertBeforeExisting = false}) {
   var edits = sourceFileEdit.edits;
+  var length = edits.length;
   var index = 0;
-  while (index < edits.length && edits[index].offset > sourceEdit.offset) {
+  while (index < length && edits[index].offset > sourceEdit.offset) {
     index++;
   }
+  // If it's an insert and it should be inserted before existing edits, also
+  // skip over any with the same offset.
+  if (insertBeforeExisting && sourceEdit.length == 0) {
+    while (index < length && edits[index].offset >= sourceEdit.offset) {
+      index++;
+    }
+  }
   edits.insert(index, sourceEdit);
 }
 
 /// Adds [edit] to the [FileEdit] for the given [file].
+///
+/// If [insertBeforeExisting] is `true`, inserts made at the same offset as
+/// other edits will be inserted such that they appear before them in the
+/// resulting document.
 void addEditToSourceChange(
-    SourceChange change, String file, int fileStamp, SourceEdit edit) {
+    SourceChange change, String file, int fileStamp, SourceEdit edit,
+    {bool insertBeforeExisting = false}) {
   var fileEdit = change.getFileEdit(file);
   if (fileEdit == null) {
     fileEdit = SourceFileEdit(file, fileStamp);
     change.addFileEdit(fileEdit);
   }
-  fileEdit.add(edit);
+  fileEdit.add(edit, insertBeforeExisting: insertBeforeExisting);
 }
 
 /// Get the result of applying the edit to the given [code]. Access via
diff --git a/pkg/analyzer_plugin/lib/protocol/protocol_common.dart b/pkg/analyzer_plugin/lib/protocol/protocol_common.dart
index 5ca19e2..32fe3b2 100644
--- a/pkg/analyzer_plugin/lib/protocol/protocol_common.dart
+++ b/pkg/analyzer_plugin/lib/protocol/protocol_common.dart
@@ -4373,8 +4373,14 @@
   }
 
   /// Adds [edit] to the [FileEdit] for the given [file].
-  void addEdit(String file, int fileStamp, SourceEdit edit) =>
-      addEditToSourceChange(this, file, fileStamp, edit);
+  ///
+  /// If [insertBeforeExisting] is `true`, inserts made at the same offset as
+  /// other edits will be inserted such that they appear before them in the
+  /// resulting document.
+  void addEdit(String file, int fileStamp, SourceEdit edit,
+          {bool insertBeforeExisting = false}) =>
+      addEditToSourceChange(this, file, fileStamp, edit,
+          insertBeforeExisting: insertBeforeExisting);
 
   /// Adds the given [FileEdit].
   void addFileEdit(SourceFileEdit edit) {
@@ -4597,10 +4603,22 @@
   }
 
   /// Adds the given [Edit] to the list.
-  void add(SourceEdit edit) => addEditForSource(this, edit);
+  ///
+  /// If [insertBeforeExisting] is `true`, inserts made at the same offset as
+  /// other edits will be inserted such that they appear before them in the
+  /// resulting document.
+  void add(SourceEdit edit, {bool insertBeforeExisting = false}) =>
+      addEditForSource(this, edit, insertBeforeExisting: insertBeforeExisting);
 
   /// Adds the given [Edit]s.
-  void addAll(Iterable<SourceEdit> edits) => addAllEditsForSource(this, edits);
+  ///
+  /// If [insertBeforeExisting] is `true`, inserts made at the same offset as
+  /// other edits will be inserted such that they appear before them in the
+  /// resulting document.
+  void addAll(Iterable<SourceEdit> edits,
+          {bool insertBeforeExisting = false}) =>
+      addAllEditsForSource(this, edits,
+          insertBeforeExisting: insertBeforeExisting);
 
   @override
   String toString() => json.encode(toJson());
diff --git a/pkg/analyzer_plugin/lib/src/protocol/protocol_internal.dart b/pkg/analyzer_plugin/lib/src/protocol/protocol_internal.dart
index 777ccda..262911a 100644
--- a/pkg/analyzer_plugin/lib/src/protocol/protocol_internal.dart
+++ b/pkg/analyzer_plugin/lib/src/protocol/protocol_internal.dart
@@ -14,26 +14,43 @@
     HashMap<String, RefactoringKind>();
 
 /// Adds the given [sourceEdits] to the list in [sourceFileEdit].
+///
+/// If [insertBeforeExisting] is `true`, inserts made at the same offset as
+/// other edits will be inserted such that they appear before them in the
+/// resulting document.
 void addAllEditsForSource(
-    SourceFileEdit sourceFileEdit, Iterable<SourceEdit> edits) {
-  edits.forEach(sourceFileEdit.add);
+    SourceFileEdit sourceFileEdit, Iterable<SourceEdit> edits,
+    {bool insertBeforeExisting = false}) {
+  edits.forEach((edit) =>
+      sourceFileEdit.add(edit, insertBeforeExisting: insertBeforeExisting));
 }
 
 /// Adds the given [sourceEdit] to the list in [sourceFileEdit] while preserving
-/// two invariants:
+/// the invariants:
 /// - the list is sorted such that edits with a larger offset appear earlier in
 ///   the list, and
-/// - no two edits in the list overlap each other.
+/// - no two edits in the list overlap each other, and
+/// - inserts can only be made at the same offset as an earlier edit when
+///   [insertBeforeExisting] is `true` and will result in the inserted text
+///   appearing before the other edits in the resulting document.
 ///
 /// If the invariants can't be preserved, then a [ConflictingEditException] is
 /// thrown.
-void addEditForSource(SourceFileEdit sourceFileEdit, SourceEdit sourceEdit) {
+void addEditForSource(SourceFileEdit sourceFileEdit, SourceEdit sourceEdit,
+    {bool insertBeforeExisting = false}) {
   var edits = sourceFileEdit.edits;
   var length = edits.length;
   var index = 0;
   while (index < length && edits[index].offset > sourceEdit.offset) {
     index++;
   }
+  // If it's an insert and it should be inserted before existing edits, also
+  // skip over any with the same offset.
+  if (insertBeforeExisting && sourceEdit.length == 0) {
+    while (index < length && edits[index].offset >= sourceEdit.offset) {
+      index++;
+    }
+  }
   if (index > 0) {
     var previousEdit = edits[index - 1];
     // The [previousEdit] has an offset that is strictly greater than the offset
@@ -63,14 +80,19 @@
 }
 
 /// Adds [edit] to the [FileEdit] for the given [file].
+///
+/// If [insertBeforeExisting] is `true`, inserts made at the same offset as
+/// other edits will be inserted such that they appear before them in the
+/// resulting document.
 void addEditToSourceChange(
-    SourceChange change, String file, int fileStamp, SourceEdit edit) {
+    SourceChange change, String file, int fileStamp, SourceEdit edit,
+    {bool insertBeforeExisting = false}) {
   var fileEdit = change.getFileEdit(file);
   if (fileEdit == null) {
     fileEdit = SourceFileEdit(file, fileStamp);
     change.addFileEdit(fileEdit);
   }
-  fileEdit.add(edit);
+  fileEdit.add(edit, insertBeforeExisting: insertBeforeExisting);
 }
 
 /// Get the result of applying the edit to the given [code]. Access via
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 a1bd021..be0b7fc 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
@@ -458,12 +458,13 @@
   }
 
   @override
-  void addInsertion(int offset, void Function(EditBuilder builder) buildEdit) {
+  void addInsertion(int offset, void Function(EditBuilder builder) buildEdit,
+      {bool insertBeforeExisting = false}) {
     var builder = createEditBuilder(offset, 0);
     try {
       buildEdit(builder);
     } finally {
-      _addEditBuilder(builder);
+      _addEditBuilder(builder, insertBeforeExisting: insertBeforeExisting);
     }
   }
 
@@ -542,8 +543,8 @@
 
   /// Add the edit from the given [edit] to the edits associated with the
   /// current file.
-  void _addEdit(SourceEdit edit) {
-    fileEdit.add(edit);
+  void _addEdit(SourceEdit edit, {bool insertBeforeExisting = false}) {
+    fileEdit.add(edit, insertBeforeExisting: insertBeforeExisting);
     var delta = _editDelta(edit);
     changeBuilder._updatePositions(edit.offset, delta);
     changeBuilder._lockedPositions.clear();
@@ -551,9 +552,14 @@
 
   /// Add the edit from the given [builder] to the edits associated with the
   /// current file.
-  void _addEditBuilder(EditBuilderImpl builder) {
+  ///
+  /// If [insertBeforeExisting] is `true`, inserts made at the same offset as
+  /// other edits will be inserted such that they appear before them in the
+  /// resulting document.
+  void _addEditBuilder(EditBuilderImpl builder,
+      {bool insertBeforeExisting = false}) {
     var edit = builder.sourceEdit;
-    _addEdit(edit);
+    _addEdit(edit, insertBeforeExisting: insertBeforeExisting);
     _captureSelection(builder, edit);
   }
 
diff --git a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart
index cee17a3..3397ab6 100644
--- a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart
+++ b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_dart.dart
@@ -1356,9 +1356,11 @@
 
   @override
   void addInsertion(
-          int offset, void Function(DartEditBuilder builder) buildEdit) =>
+          int offset, void Function(DartEditBuilder builder) buildEdit,
+          {bool insertBeforeExisting = false}) =>
       super.addInsertion(
-          offset, (builder) => buildEdit(builder as DartEditBuilder));
+          offset, (builder) => buildEdit(builder as DartEditBuilder),
+          insertBeforeExisting: insertBeforeExisting);
 
   @override
   void addReplacement(SourceRange range,
@@ -1679,19 +1681,36 @@
     if (unit.declarations.isNotEmpty) {
       offset = unit.declarations.first.offset;
       insertEmptyLineAfter = true;
+    } else if (fileEdit.edits.isNotEmpty) {
+      // If this file has edits (besides the imports) the imports should go
+      // at the same offset as those edits and _not_ at `unit.end`. This is
+      // because if the document is non-zero length, `unit.end` could be after
+      // where the new edits will be inserted, but imports should go before
+      // generated non-import code.
+
+      // Edits are always sorted such that the first one has the lowest offset.
+      offset = fileEdit.edits.first.offset;
+
+      // Also ensure there's a blank line between the imports and the other
+      // code.
+      insertEmptyLineAfter = fileEdit.edits.isNotEmpty;
     } else {
       offset = unit.end;
     }
-    addInsertion(offset, (EditBuilder builder) {
-      for (var i = 0; i < importList.length; i++) {
-        var import = importList[i];
-        writeImport(builder, import);
-        builder.writeln();
-        if (i == importList.length - 1 && insertEmptyLineAfter) {
+    addInsertion(
+      offset,
+      (EditBuilder builder) {
+        for (var i = 0; i < importList.length; i++) {
+          var import = importList[i];
+          writeImport(builder, import);
           builder.writeln();
+          if (i == importList.length - 1 && insertEmptyLineAfter) {
+            builder.writeln();
+          }
         }
-      }
-    });
+      },
+      insertBeforeExisting: true,
+    );
   }
 
   /// Return the import element used to import the given [element] into the
diff --git a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_yaml.dart b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_yaml.dart
index 6e37763..a941bae 100644
--- a/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_yaml.dart
+++ b/pkg/analyzer_plugin/lib/src/utilities/change_builder/change_builder_yaml.dart
@@ -48,9 +48,11 @@
 
   @override
   void addInsertion(
-          int offset, void Function(YamlEditBuilder builder) buildEdit) =>
+          int offset, void Function(YamlEditBuilder builder) buildEdit,
+          {bool insertBeforeExisting = false}) =>
       super.addInsertion(
-          offset, (builder) => buildEdit(builder as YamlEditBuilder));
+          offset, (builder) => buildEdit(builder as YamlEditBuilder),
+          insertBeforeExisting: insertBeforeExisting);
 
   @override
   void addReplacement(SourceRange range,
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 c7211d4..bd2700d 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
@@ -678,9 +678,10 @@
     expect(edit.replacement, equalsIgnoringWhitespace('Foo'));
   }
 
-  Future<void> test_writeImportedName_needsImport() async {
+  Future<void> test_writeImportedName_needsImport_insertion() async {
     var path = convertPath('/home/test/lib/test.dart');
-    addSource(path, '');
+    var content = '';
+    addSource(path, content);
 
     var builder = newBuilder();
     await builder.addDartFileEdit(path, (builder) {
@@ -693,9 +694,40 @@
     });
     var edits = getEdits(builder);
     expect(edits, hasLength(2));
-    expect(edits[0].replacement,
+    expect(edits[0].replacement, equalsIgnoringWhitespace('Foo'));
+    expect(edits[1].replacement,
         equalsIgnoringWhitespace("import 'package:test/foo.dart';\n"));
-    expect(edits[1].replacement, equalsIgnoringWhitespace('Foo'));
+    var result = SourceEdit.applySequence(content, edits);
+    expect(result, '''
+import 'package:test/foo.dart';
+
+Foo''');
+  }
+
+  Future<void> test_writeImportedName_needsImport_replacement() async {
+    var path = convertPath('/home/test/lib/test.dart');
+    var content = 'test';
+    addSource(path, content);
+
+    var builder = newBuilder();
+    await builder.addDartFileEdit(path, (builder) {
+      builder.addReplacement(SourceRange(0, 4), (builder) {
+        builder.writeImportedName([
+          Uri.parse('package:test/foo.dart'),
+          Uri.parse('package:test/bar.dart')
+        ], 'Foo');
+      });
+    });
+    var edits = getEdits(builder);
+    expect(edits, hasLength(2));
+    expect(edits[0].replacement, equalsIgnoringWhitespace('Foo'));
+    expect(edits[1].replacement,
+        equalsIgnoringWhitespace("import 'package:test/foo.dart';\n"));
+    var result = SourceEdit.applySequence(content, edits);
+    expect(result, '''
+import 'package:test/foo.dart';
+
+Foo''');
   }
 
   Future<void> test_writeLocalVariableDeclaration_noType_initializer() async {
@@ -1257,14 +1289,19 @@
 
     var builder = newBuilder();
     await builder.addDartFileEdit(path, (builder) {
-      builder.addInsertion(content.length - 1, (builder) {
+      builder.addInsertion(0, (builder) {
         builder.writeReference(aElement);
       });
     });
     var edits = getEdits(builder);
     expect(edits, hasLength(2));
-    expect(edits[0].replacement, equalsIgnoringWhitespace("import 'a.dart';"));
-    expect(edits[1].replacement, equalsIgnoringWhitespace('a'));
+    expect(edits[0].replacement, equalsIgnoringWhitespace('a'));
+    expect(edits[1].replacement, equalsIgnoringWhitespace("import 'a.dart';"));
+    var result = SourceEdit.applySequence(content, edits);
+    expect(result, '''
+import 'a.dart';
+
+a''');
   }
 
   Future<void> test_writeSetterDeclaration_bodyWriter() async {
@@ -1528,7 +1565,7 @@
 
     var builder = newBuilder();
     await builder.addDartFileEdit(path, (builder) {
-      builder.addInsertion(content.length - 1, (builder) {
+      builder.addInsertion(0, (builder) {
         builder.writeType(a1.instantiate(
           typeArguments: [],
           nullabilitySuffix: NullabilitySuffix.star,
@@ -1552,12 +1589,19 @@
     expect(edits, hasLength(2));
     expect(
         edits[0].replacement,
-        equalsIgnoringWhitespace("import 'package:test/a.dart' as _prefix0; "
-            "import 'package:test/b.dart' as _prefix1;"));
-    expect(
-        edits[1].replacement,
         equalsIgnoringWhitespace(
             '_prefix0.A1 a1; _prefix0.A2 a2; _prefix1.B b;'));
+    expect(
+        edits[1].replacement,
+        equalsIgnoringWhitespace("import 'package:test/a.dart' as _prefix0; "
+            "import 'package:test/b.dart' as _prefix1;"));
+
+    var resultCode = SourceEdit.applySequence(content, edits);
+    expect(resultCode, r'''
+import 'package:test/a.dart' as _prefix0;
+import 'package:test/b.dart' as _prefix1;
+
+_prefix0.A1 a1; _prefix0.A2 a2; _prefix1.B b;''');
   }
 
   Future<void> test_writeType_required_dynamic() async {
diff --git a/pkg/analyzer_plugin/tool/spec/codegen_dart_protocol.dart b/pkg/analyzer_plugin/tool/spec/codegen_dart_protocol.dart
index c5f4cb9..e99476e 100644
--- a/pkg/analyzer_plugin/tool/spec/codegen_dart_protocol.dart
+++ b/pkg/analyzer_plugin/tool/spec/codegen_dart_protocol.dart
@@ -713,10 +713,20 @@
         writeln('}');
         return true;
       case 'SourceChange':
-        docComment(
-            [dom.Text('Adds [edit] to the [FileEdit] for the given [file].')]);
-        writeln('void addEdit(String file, int fileStamp, SourceEdit edit) =>');
-        writeln('    addEditToSourceChange(this, file, fileStamp, edit);');
+        docComment([
+          dom.Element.tag('p')
+            ..append(dom.Text(
+                'Adds [edit] to the [FileEdit] for the given [file].')),
+          dom.Element.tag('p')
+            ..append(dom.Text(
+                'If [insertBeforeExisting] is `true`, inserts made at the '
+                'same offset as other edits will be inserted such that they '
+                'appear before them in the resulting document.')),
+        ]);
+        writeln('void addEdit(String file, int fileStamp, SourceEdit edit, '
+            '{bool insertBeforeExisting = false}) =>');
+        writeln('    addEditToSourceChange(this, file, fileStamp, edit, '
+            'insertBeforeExisting: insertBeforeExisting);');
         writeln();
         docComment([dom.Text('Adds the given [FileEdit].')]);
         writeln('void addFileEdit(SourceFileEdit edit) {');
@@ -745,12 +755,32 @@
         writeln('String apply(String code) => applyEdit(code, this);');
         return true;
       case 'SourceFileEdit':
-        docComment([dom.Text('Adds the given [Edit] to the list.')]);
-        writeln('void add(SourceEdit edit) => addEditForSource(this, edit);');
+        docComment([
+          dom.Element.tag('p')
+            ..append(dom.Text('Adds the given [Edit] to the list.')),
+          dom.Element.tag('p')
+            ..append(dom.Text(
+                'If [insertBeforeExisting] is `true`, inserts made at the '
+                'same offset as other edits will be inserted such that they '
+                'appear before them in the resulting document.')),
+        ]);
+        writeln('void add(SourceEdit edit, '
+            '{bool insertBeforeExisting = false}) =>');
+        writeln('    addEditForSource(this, edit, '
+            'insertBeforeExisting: insertBeforeExisting);');
         writeln();
-        docComment([dom.Text('Adds the given [Edit]s.')]);
-        writeln('void addAll(Iterable<SourceEdit> edits) =>');
-        writeln('    addAllEditsForSource(this, edits);');
+        docComment([
+          dom.Element.tag('p')..append(dom.Text('Adds the given [Edit]s.')),
+          dom.Element.tag('p')
+            ..append(dom.Text(
+                'If [insertBeforeExisting] is `true`, inserts made at the '
+                'same offset as other edits will be inserted such that they '
+                'appear before them in the resulting document.')),
+        ]);
+        writeln('void addAll(Iterable<SourceEdit> edits, '
+            '{bool insertBeforeExisting = false}) =>');
+        writeln('    addAllEditsForSource(this, edits, '
+            'insertBeforeExisting: insertBeforeExisting);');
         return true;
       default:
         return false;
diff --git a/tools/VERSION b/tools/VERSION
index e69a453..25a16b1 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 17
 PATCH 0
-PRERELEASE 163
+PRERELEASE 164
 PRERELEASE_PATCH 0
\ No newline at end of file