Issue 30577. Use AST to find position for the first directive.

R=brianwilkerson@google.com

Bug: https://github.com/dart-lang/sdk/issues/30577
Change-Id: If69df5f49228a3b698544dcf82ad0c34c1be84e8
Reviewed-on: https://dart-review.googlesource.com/54307
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
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 c429a6d..b9b3f80 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
@@ -1424,19 +1424,22 @@
       return;
     }
 
-    // If still at the beginning of the file, skip shebang and line comments.
-    _InsertionDescription desc = _getInsertDescTop();
-    int offset = desc.offset;
+    // If still at the beginning of the file, add before the first declaration.
+    int offset;
+    bool insertEmptyLineAfter = false;
+    if (unit.declarations.isNotEmpty) {
+      offset = unit.declarations.first.offset;
+      insertEmptyLineAfter = true;
+    } else {
+      offset = unit.end;
+    }
     for (int i = 0; i < uriList.length; i++) {
       String importUri = uriList[i];
       addInsertion(offset, (EditBuilder builder) {
-        if (i == 0 && desc.insertEmptyLineBefore) {
-          builder.writeln();
-        }
         builder.write("import '");
         builder.write(importUri);
         builder.writeln("';");
-        if (i == uriList.length - 1 && desc.insertEmptyLineAfter) {
+        if (i == uriList.length - 1 && insertEmptyLineAfter) {
           builder.writeln();
         }
       });
@@ -1444,63 +1447,6 @@
   }
 
   /**
-   * Returns an insertion description describing where to insert a new directive
-   * or a top-level declaration at the top of the file.
-   */
-  _InsertionDescription _getInsertDescTop() {
-    // skip leading line comments
-    int offset = 0;
-    bool insertEmptyLineBefore = false;
-    bool insertEmptyLineAfter = false;
-    String source = unit.element.context.getContents(unit.element.source).data;
-    var lineInfo = unit.lineInfo;
-    // skip hash-bang
-    if (offset < source.length - 2) {
-      String linePrefix = _getText(source, offset, 2);
-      if (linePrefix == "#!") {
-        insertEmptyLineBefore = true;
-        offset = lineInfo.getOffsetOfLineAfter(offset);
-        // skip empty lines to first line comment
-        int emptyOffset = offset;
-        while (emptyOffset < source.length - 2) {
-          int nextLineOffset = lineInfo.getOffsetOfLineAfter(emptyOffset);
-          String line = source.substring(emptyOffset, nextLineOffset);
-          if (line.trim().isEmpty) {
-            emptyOffset = nextLineOffset;
-            continue;
-          } else if (line.startsWith("//")) {
-            offset = emptyOffset;
-            break;
-          } else {
-            break;
-          }
-        }
-      }
-    }
-    // skip line comments
-    while (offset < source.length - 2) {
-      String linePrefix = _getText(source, offset, 2);
-      if (linePrefix == "//") {
-        insertEmptyLineBefore = true;
-        offset = lineInfo.getOffsetOfLineAfter(offset);
-      } else {
-        break;
-      }
-    }
-    // determine if empty line is required after
-    int currentLine = lineInfo.getLocation(offset).lineNumber;
-    if (currentLine + 1 < lineInfo.lineCount) {
-      int nextLineOffset = lineInfo.getOffsetOfLine(currentLine + 1);
-      String insertLine = source.substring(offset, nextLineOffset);
-      if (!insertLine.trim().isEmpty) {
-        insertEmptyLineAfter = true;
-      }
-    }
-    return new _InsertionDescription(
-        offset, insertEmptyLineBefore, insertEmptyLineAfter);
-  }
-
-  /**
    * Computes the best URI to import [what] into [from].
    */
   String _getLibrarySourceUri(LibraryElement from, Source what) {
@@ -1518,13 +1464,6 @@
   }
 
   /**
-   * Returns the text of the given range in the unit.
-   */
-  String _getText(String content, int offset, int length) {
-    return content.substring(offset, offset + length);
-  }
-
-  /**
    * Create an edit to replace the return type of the innermost function
    * containing the given [node] with the type `Future`. The [typeProvider] is
    * used to check the current return type, because if it is already `Future` no
@@ -1687,11 +1626,3 @@
     return null;
   }
 }
-
-class _InsertionDescription {
-  final int offset;
-  final bool insertEmptyLineBefore;
-  final bool insertEmptyLineAfter;
-  _InsertionDescription(
-      this.offset, this.insertEmptyLineBefore, this.insertEmptyLineAfter);
-}
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 4859430..d8d76f8 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
@@ -154,6 +154,50 @@
 ''');
   }
 
+  test_importLibrary_noDirectives_docComment() async {
+    await _assertImportLibraries('''
+/// Documentation comment.
+/// Continues.
+void main() {}
+''', ['dart:async'], '''
+import 'dart:async';
+
+/// Documentation comment.
+/// Continues.
+void main() {}
+''');
+  }
+
+  test_importLibrary_noDirectives_hashBang() async {
+    await _assertImportLibraries('''
+#!/bin/dart
+
+void main() {}
+''', ['dart:async'], '''
+#!/bin/dart
+
+import 'dart:async';
+
+void main() {}
+''');
+  }
+
+  test_importLibrary_noDirectives_lineComment() async {
+    await _assertImportLibraries('''
+// Not documentation comment.
+// Continues.
+
+void main() {}
+''', ['dart:async'], '''
+// Not documentation comment.
+// Continues.
+
+import 'dart:async';
+
+void main() {}
+''');
+  }
+
   test_importLibrary_package_afterDart() async {
     await _assertImportLibraries('''
 import 'dart:async';