Support for optional ImportPrefixGenerator to generate prefixes for new imports.

Only one import is generated for each library.
If there is already an import for a library, new prefix is not asked.
If a prefix was generated once, it is not possible to force another
import with a different prefix.

R=brianwilkerson@google.com

Change-Id: I1676a1953002060b5e7155ce90abf87b3bfe270e
Reviewed-on: https://dart-review.googlesource.com/55326
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 06e9aaa..d90184f 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
@@ -42,8 +42,14 @@
 
   @override
   Future<Null> addFileEdit(
-          String path, void buildFileEdit(DartFileEditBuilder builder)) =>
-      super.addFileEdit(path, (builder) => buildFileEdit(builder));
+      String path, void buildFileEdit(DartFileEditBuilder builder),
+      {ImportPrefixGenerator importPrefixGenerator}) {
+    return super.addFileEdit(path, (builder) {
+      DartFileEditBuilderImpl dartBuilder = builder;
+      dartBuilder.importPrefixGenerator = importPrefixGenerator;
+      buildFileEdit(dartBuilder);
+    });
+  }
 
   @override
   Future<DartFileEditBuilderImpl> createFileEditBuilder(String path) async {
@@ -87,13 +93,6 @@
    */
   String getIndent(int level) => '  ' * level;
 
-  /**
-   * Arrange to have an import added for the given [library].
-   */
-  void importLibrary(Uri library) {
-    dartFileEditBuilder.importLibrary(library);
-  }
-
   @override
   void writeClassDeclaration(String name,
       {Iterable<DartType> interfaces,
@@ -595,7 +594,7 @@
           write('.');
         }
       } else {
-        importLibrary(definingLibrary.source.uri);
+        _importLibrary(definingLibrary.source.uri);
       }
     }
 
@@ -612,8 +611,11 @@
       _EnclosingElementFinder finder = new _EnclosingElementFinder();
       finder.find(dartFileEditBuilder.unit, offset);
       String typeSource = _getTypeSource(
-          type, finder.enclosingClass, finder.enclosingExecutable,
-          methodBeingCopied: methodBeingCopied);
+        type,
+        finder.enclosingClass,
+        finder.enclosingExecutable,
+        methodBeingCopied: methodBeingCopied,
+      );
       if (typeSource.isNotEmpty && typeSource != 'dynamic') {
         if (groupName != null) {
           addLinkedEdit(groupName, (LinkedEditBuilder builder) {
@@ -1021,7 +1023,11 @@
           buffer.write(".");
         }
       } else {
-        importLibrary(definingLibrary.source.uri);
+        _LibraryToImport import = _importLibrary(definingLibrary.source.uri);
+        if (import.prefix != null) {
+          buffer.write(import.prefix);
+          buffer.write('.');
+        }
       }
     }
     // append simple name
@@ -1168,6 +1174,13 @@
     }
     return type;
   }
+
+  /**
+   * Arrange to have an import added for the library with the given [uri].
+   */
+  _LibraryToImport _importLibrary(Uri uri) {
+    return dartFileEditBuilder._importLibrary(uri);
+  }
 }
 
 /**
@@ -1181,10 +1194,15 @@
   CompilationUnit unit;
 
   /**
-   * The set of absolute or relative URIs of the libraries that need to be
-   * imported in order to make visible the names used in generated code.
+   * The optional generator of prefixes for new imports.
    */
-  Set<String> librariesToImport = new Set<String>();
+  ImportPrefixGenerator importPrefixGenerator;
+
+  /**
+   * A mapping from libraries that need to be imported in order to make visible
+   * the names used in generated code, to information about these imports.
+   */
+  Map<Uri, _LibraryToImport> librariesToImport = {};
 
   /**
    * Initialize a newly created builder to build a source file edit within the
@@ -1230,23 +1248,20 @@
       CompilationUnitElement definingUnitElement =
           libraryElement.definingCompilationUnit;
       if (definingUnitElement == unitElement) {
-        _addLibraryImports(librariesToImport);
+        _addLibraryImports(librariesToImport.values);
       } else {
         await (changeBuilder as DartChangeBuilder).addFileEdit(
             definingUnitElement.source.fullName, (DartFileEditBuilder builder) {
           (builder as DartFileEditBuilderImpl)
-              ._addLibraryImports(librariesToImport);
+              ._addLibraryImports(librariesToImport.values);
         });
       }
     }
   }
 
   @override
-  String importLibrary(Uri library) {
-    LibraryElement targetLibrary = unit.element.library;
-    String uriText = _getLibrarySourceUri(targetLibrary, library);
-    librariesToImport.add(uriText);
-    return uriText;
+  String importLibrary(Uri uri) {
+    return _importLibrary(uri).uriText;
   }
 
   @override
@@ -1276,9 +1291,10 @@
   }
 
   /**
-   * Adds edits ensure that all the [libraries] are imported.
+   * Adds edits ensure that all the [imports] are imported into the given
+   * [targetLibrary].
    */
-  void _addLibraryImports(Set<String> libraries) {
+  void _addLibraryImports(Iterable<_LibraryToImport> imports) {
     // Prepare information about existing imports.
     LibraryDirective libraryDirective;
     List<ImportDirective> importDirectives = <ImportDirective>[];
@@ -1290,21 +1306,31 @@
       }
     }
 
-    // Prepare all URIs to import.
-    List<String> uriList = libraries.toList();
-    uriList.sort((a, b) => a.compareTo(b));
+    // Sort imports by URIs.
+    List<_LibraryToImport> importList = imports.toList();
+    importList.sort((a, b) => a.uriText.compareTo(b.uriText));
+
+    void writeImport(EditBuilder builder, _LibraryToImport import) {
+      builder.write("import '");
+      builder.write(import.uriText);
+      builder.write("'");
+      if (import.prefix != null) {
+        builder.write(' as ');
+        builder.write(import.prefix);
+      }
+      builder.write(';');
+    }
 
     // Insert imports: between existing imports.
     if (importDirectives.isNotEmpty) {
-      for (String importUri in uriList) {
-        bool isDart = importUri.startsWith('dart:');
-        bool isPackage = importUri.startsWith('package:');
+      for (var import in importList) {
+        bool isDart = import.uriText.startsWith('dart:');
+        bool isPackage = import.uriText.startsWith('package:');
         bool inserted = false;
 
         void insert(
             {ImportDirective prev,
             ImportDirective next,
-            String uri,
             bool trailingNewLine: false}) {
           LineInfo lineInfo = unit.lineInfo;
           if (prev != null) {
@@ -1319,16 +1345,13 @@
             }
             addInsertion(offset, (EditBuilder builder) {
               builder.writeln();
-              builder.write("import '");
-              builder.write(uri);
-              builder.write("';");
+              writeImport(builder, import);
             });
           } else {
             int offset = next.offset;
             addInsertion(offset, (EditBuilder builder) {
-              builder.write("import '");
-              builder.write(uri);
-              builder.writeln("';");
+              writeImport(builder, import);
+              builder.writeln();
               if (trailingNewLine) {
                 builder.writeln();
               }
@@ -1349,14 +1372,13 @@
           bool isExistingPackage = existingUri.startsWith('package:');
           bool isExistingRelative = !existingUri.contains(':');
 
-          bool isNewBeforeExisting = importUri.compareTo(existingUri) < 0;
+          bool isNewBeforeExisting = import.uriText.compareTo(existingUri) < 0;
 
           if (isDart) {
             if (!isExistingDart || isNewBeforeExisting) {
               insert(
                   prev: lastExistingDart,
                   next: existingImport,
-                  uri: importUri,
                   trailingNewLine: !isExistingDart);
               break;
             }
@@ -1365,13 +1387,12 @@
               insert(
                   prev: lastExistingPackage,
                   next: existingImport,
-                  uri: importUri,
                   trailingNewLine: isExistingRelative);
               break;
             }
           } else {
             if (!isExistingDart && !isExistingPackage && isNewBeforeExisting) {
-              insert(next: existingImport, uri: importUri);
+              insert(next: existingImport);
               break;
             }
           }
@@ -1397,9 +1418,7 @@
               }
             }
             builder.writeln();
-            builder.write("import '");
-            builder.write(importUri);
-            builder.write("';");
+            writeImport(builder, import);
           });
         }
       }
@@ -1408,18 +1427,17 @@
 
     // Insert imports: after the library directive.
     if (libraryDirective != null) {
-      for (int i = 0; i < uriList.length; i++) {
-        String importUri = uriList[i];
-        addInsertion(libraryDirective.end, (EditBuilder builder) {
+      addInsertion(libraryDirective.end, (EditBuilder builder) {
+        for (int i = 0; i < importList.length; i++) {
+          var import = importList[i];
           if (i == 0) {
             builder.writeln();
           }
           builder.writeln();
-          builder.write("import '");
-          builder.write(importUri);
-          builder.writeln("';");
-        });
-      }
+          writeImport(builder, import);
+          builder.writeln();
+        }
+      });
       return;
     }
 
@@ -1432,17 +1450,16 @@
     } else {
       offset = unit.end;
     }
-    for (int i = 0; i < uriList.length; i++) {
-      String importUri = uriList[i];
-      addInsertion(offset, (EditBuilder builder) {
-        builder.write("import '");
-        builder.write(importUri);
-        builder.writeln("';");
-        if (i == uriList.length - 1 && insertEmptyLineAfter) {
+    addInsertion(offset, (EditBuilder builder) {
+      for (int i = 0; i < importList.length; i++) {
+        var import = importList[i];
+        writeImport(builder, import);
+        builder.writeln();
+        if (i == importList.length - 1 && insertEmptyLineAfter) {
           builder.writeln();
         }
-      });
-    }
+      }
+    });
   }
 
   /**
@@ -1460,6 +1477,22 @@
   }
 
   /**
+   * Arrange to have an import added for the library with the given [uri].
+   */
+  _LibraryToImport _importLibrary(Uri uri) {
+    var import = librariesToImport[uri];
+    if (import == null) {
+      LibraryElement targetLibrary = unit.element.library;
+      String uriText = _getLibrarySourceUri(targetLibrary, uri);
+      String prefix =
+          importPrefixGenerator != null ? importPrefixGenerator(uri) : null;
+      import = new _LibraryToImport(uriText, prefix);
+      librariesToImport[uri] = import;
+    }
+    return import;
+  }
+
+  /**
    * 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
@@ -1622,3 +1655,23 @@
     return null;
   }
 }
+
+/**
+ * Information about a new library to import.
+ */
+class _LibraryToImport {
+  final String uriText;
+  final String prefix;
+
+  _LibraryToImport(this.uriText, this.prefix);
+
+  @override
+  int get hashCode => uriText.hashCode;
+
+  @override
+  bool operator ==(other) {
+    return other is _LibraryToImport &&
+        other.uriText == uriText &&
+        other.prefix == prefix;
+  }
+}
diff --git a/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart b/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart
index 3f24874..4cd16ae 100644
--- a/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart
+++ b/pkg/analyzer_plugin/lib/utilities/change_builder/change_builder_dart.dart
@@ -14,6 +14,11 @@
 import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
 
 /**
+ * The optional generator for prefix that should be used for new imports.
+ */
+typedef ImportPrefixGenerator = String Function(Uri);
+
+/**
  * A [ChangeBuilder] used to build changes in Dart files.
  *
  * Clients may not extend, implement or mix-in this class.
@@ -24,9 +29,18 @@
    */
   factory DartChangeBuilder(AnalysisSession session) = DartChangeBuilderImpl;
 
+  /**
+   * Use the [buildFileEdit] function to create a collection of edits to the
+   * file with the given [path]. The edits will be added to the source change
+   * that is being built.
+   *
+   * If [importPrefixGenerator] is provided, it will be asked to generate an
+   * import prefix for every newly imported library.
+   */
   @override
   Future<Null> addFileEdit(
-      String path, void buildFileEdit(DartFileEditBuilder builder));
+      String path, void buildFileEdit(DartFileEditBuilder builder),
+      {ImportPrefixGenerator importPrefixGenerator});
 }
 
 /**
@@ -305,12 +319,12 @@
       FunctionBody body, TypeProvider typeProvider);
 
   /**
-   * Arrange to have an import added for the given [library].
+   * Arrange to have an import added for the library with the given [uri].
    *
    * Returns the text of the URI that will be used in the import directive.
    * It can be different than the given [Uri].
    */
-  String importLibrary(Uri library);
+  String importLibrary(Uri uri);
 
   /**
    * Optionally create an edit to replace the given [typeAnnotation] with the
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 d0dfbc4..82999a6 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
@@ -1821,6 +1821,56 @@
     expect(edit.replacement, equalsIgnoringWhitespace(''));
   }
 
+  test_writeType_prefixGenerator() async {
+    String aPath = provider.convertPath('/a.dart');
+    String bPath = provider.convertPath('/b.dart');
+
+    addSource(aPath, r'''
+class A1 {}
+class A2 {}
+''');
+    addSource(bPath, r'''
+class B {}
+''');
+
+    String path = provider.convertPath('/test.dart');
+    String content = '';
+    addSource(path, content);
+
+    ClassElement a1 = await _getClassElement(aPath, 'A1');
+    ClassElement a2 = await _getClassElement(aPath, 'A2');
+    ClassElement b = await _getClassElement(bPath, 'B');
+
+    int nextPrefixIndex = 0;
+    String prefixGenerator(_) {
+      return '_prefix${nextPrefixIndex++}';
+    }
+
+    var builder = new DartChangeBuilder(session);
+    await builder.addFileEdit(path, (builder) {
+      builder.addInsertion(content.length - 1, (builder) {
+        builder.writeType(a1.type);
+        builder.write(' a1; ');
+
+        builder.writeType(a2.type);
+        builder.write(' a2; ');
+
+        builder.writeType(b.type);
+        builder.write(' b;');
+      });
+    }, importPrefixGenerator: prefixGenerator);
+    List<SourceEdit> edits = getEdits(builder);
+    expect(edits, hasLength(2));
+    expect(
+        edits[0].replacement,
+        equalsIgnoringWhitespace(
+            "import 'a.dart' as _prefix0; import 'b.dart' as _prefix1;"));
+    expect(
+        edits[1].replacement,
+        equalsIgnoringWhitespace(
+            '_prefix0.A1 a1; _prefix0.A2 a2; _prefix1.B b;'));
+  }
+
   test_writeType_required_dynamic() async {
     String path = provider.convertPath('/test.dart');
     String content = 'class A {}';