Rewrite writing types to using existing writeXYZ() methods.

Also extract code that ensures imports and writes prefixes into a
separate method.

R=brianwilkerson@google.com

Change-Id: Icd35af19435153bfaebd46f7611823e6a9830d20
Reviewed-on: https://dart-review.googlesource.com/55640
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/test/services/correction/assist_test.dart b/pkg/analysis_server/test/services/correction/assist_test.dart
index c80d7e9..752c8af 100644
--- a/pkg/analysis_server/test/services/correction/assist_test.dart
+++ b/pkg/analysis_server/test/services/correction/assist_test.dart
@@ -518,7 +518,7 @@
 ''');
     await assertHasAssistAt('v =', DartAssistKind.ADD_TYPE_ANNOTATION, '''
 main() {
-  Function v = () => 1;
+  int Function() v = () => 1;
 }
 ''');
   }
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 18ac377..c465304 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
@@ -488,17 +488,23 @@
   @override
   void writeParameter(String name,
       {ExecutableElement methodBeingCopied, DartType type}) {
-    String parameterSource;
     if (type != null) {
-      _EnclosingElementFinder finder = new _EnclosingElementFinder();
-      finder.find(dartFileEditBuilder.unit, offset);
-      parameterSource = _getTypeSource(
-          type, finder.enclosingClass, finder.enclosingExecutable,
-          parameterName: name, methodBeingCopied: methodBeingCopied);
+      _EnclosingElementFinder finder = _findEnclosingElements();
+      bool hasType = _writeType(
+        type,
+        finder.enclosingClass,
+        finder.enclosingExecutable,
+        methodBeingCopied: methodBeingCopied,
+      );
+      if (name.isNotEmpty) {
+        if (hasType) {
+          write(' ');
+        }
+        write(name);
+      }
     } else {
-      parameterSource = name;
+      write(name);
     }
-    write(parameterSource);
   }
 
   @override
@@ -598,24 +604,8 @@
   @override
   void writeReference(Element element) {
     if (element.enclosingElement is CompilationUnitElement) {
-      LibraryElement definingLibrary = element.library;
-      LibraryElement importingLibrary =
-          dartFileEditBuilder.unit.element.library;
-
-      // TODO(scheglov) Extract this code (it is already used twice).
-      // TODO(scheglov) Consider updating `show` combinator to show the element.
-      ImportElement existingImport =
-          _getImportElement(element, importingLibrary);
-      if (existingImport != null) {
-        if (existingImport.prefix != null) {
-          write(existingImport.prefix.displayName);
-          write('.');
-        }
-      } else {
-        _importLibrary(definingLibrary.source.uri);
-      }
+      _writeLibraryReference(element);
     }
-
     write(element.displayName);
   }
 
@@ -626,26 +616,29 @@
       ExecutableElement methodBeingCopied,
       bool required: false}) {
     if (type != null && !type.isDynamic) {
-      _EnclosingElementFinder finder = new _EnclosingElementFinder();
-      finder.find(dartFileEditBuilder.unit, offset);
-      String typeSource = _getTypeSource(
-        type,
-        finder.enclosingClass,
-        finder.enclosingExecutable,
-        methodBeingCopied: methodBeingCopied,
-      );
-      if (typeSource.isNotEmpty && typeSource != 'dynamic') {
-        if (groupName != null) {
-          addLinkedEdit(groupName, (LinkedEditBuilder builder) {
-            write(typeSource);
-            if (addSupertypeProposals) {
-              _addSuperTypeProposals(builder, type, new Set<DartType>());
-            }
-          });
-        } else {
-          write(typeSource);
-        }
-        return true;
+      _EnclosingElementFinder finder = _findEnclosingElements();
+      if (groupName != null) {
+        bool wroteType;
+        addLinkedEdit(groupName, (LinkedEditBuilder builder) {
+          // TODO(scheglov) ensure that no linked edit if nothing written
+          wroteType = _writeType(
+            type,
+            finder.enclosingClass,
+            finder.enclosingExecutable,
+            methodBeingCopied: methodBeingCopied,
+          );
+          if (wroteType && addSupertypeProposals) {
+            _addSuperTypeProposals(builder, type, new Set<DartType>());
+          }
+        });
+        return wroteType;
+      } else {
+        return _writeType(
+          type,
+          finder.enclosingClass,
+          finder.enclosingExecutable,
+          methodBeingCopied: methodBeingCopied,
+        );
       }
     }
     if (required) {
@@ -660,15 +653,11 @@
       {ExecutableElement methodBeingCopied}) {
     write(typeParameter.name);
     if (typeParameter.bound != null) {
-      _EnclosingElementFinder finder = new _EnclosingElementFinder();
-      finder.find(dartFileEditBuilder.unit, offset);
-      String bound = _getTypeSource(typeParameter.bound, finder.enclosingClass,
+      _EnclosingElementFinder finder = _findEnclosingElements();
+      write(' extends ');
+      _writeType(typeParameter.bound, finder.enclosingClass,
           finder.enclosingExecutable,
           methodBeingCopied: methodBeingCopied);
-      if (bound != null) {
-        write(' extends ');
-        write(bound);
-      }
     }
   }
 
@@ -762,6 +751,13 @@
     }
   }
 
+  _EnclosingElementFinder _findEnclosingElements() {
+    // TODO(scheglov) Stop searching for enclosing class multiple times.
+    _EnclosingElementFinder finder = new _EnclosingElementFinder();
+    finder.find(dartFileEditBuilder.unit, offset);
+    return finder;
+  }
+
   String _getBaseNameFromExpression(Expression expression) {
     if (expression is AsExpression) {
       return _getBaseNameFromExpression(expression.expression);
@@ -904,193 +900,6 @@
   }
 
   /**
-   * Returns the source to reference [type] in this compilation unit.
-   *
-   * If an [enclosingClass] is provided then the reference is being generated
-   * within the class and the type parameters of the class will be considered to
-   * be visible.
-   *
-   * If an [enclosingExecutable] is provided, then the reference is being
-   * generated within the class and the type parameters of the method will be
-   * considered to be visible.
-   *
-   * If a [methodBeingCopied] is provided, then the type parameters of that
-   * method will be duplicated in the copy and will therefore be visible.
-   *
-   * If a [parameterName] is given, then the type is the type of a parameter
-   * and the parameter name will be included, either in-line in a function type
-   * or after the type source for other types.
-   *
-   * Causes any libraries whose elements are used by the generated source, to be
-   * imported.
-   */
-  String _getTypeSource(DartType type, ClassElement enclosingClass,
-      ExecutableElement enclosingExecutable,
-      {String parameterName, ExecutableElement methodBeingCopied}) {
-    // type parameter
-    type = _getVisibleType(type, enclosingClass, enclosingExecutable,
-        methodBeingCopied: methodBeingCopied);
-    if (type == null || type.isDynamic || type.isBottom) {
-      if (parameterName != null) {
-        return parameterName;
-      }
-      return 'dynamic';
-    }
-
-    Element element = type.element;
-
-    // Typedef(s) are represented as GenericFunctionTypeElement(s).
-    if (element is GenericFunctionTypeElement &&
-        element.typeParameters.isEmpty &&
-        element.enclosingElement is GenericTypeAliasElement) {
-      element = element.enclosingElement;
-    }
-
-    // just a Function, not FunctionTypeAliasElement
-    if (type is FunctionType && element is! FunctionTypeAliasElement) {
-      if (parameterName == null) {
-        return 'Function';
-      }
-      // TODO(brianwilkerson) Using a buffer here means that we cannot re-use
-      // the existing `write*` functions. Refactor this code to remove the
-      // duplication.
-      StringBuffer buffer = new StringBuffer();
-      String returnType = _getTypeSource(
-          type.returnType, enclosingClass, enclosingExecutable,
-          methodBeingCopied: methodBeingCopied);
-      if (returnType != null) {
-        buffer.write(returnType);
-        buffer.write(' ');
-      }
-      if (element is GenericFunctionTypeElement) {
-        buffer.write('Function');
-        if (element.typeParameters.isNotEmpty) {
-          buffer.write('<');
-          bool isFirst = true;
-          for (TypeParameterElement typeParameter in element.typeParameters) {
-            if (!isFirst) {
-              buffer.write(', ');
-            }
-            isFirst = false;
-            buffer.write(typeParameter.name);
-            if (typeParameter.bound != null) {
-              String bound = _getTypeSource(
-                  typeParameter.bound, enclosingClass, enclosingExecutable,
-                  methodBeingCopied: methodBeingCopied);
-              if (bound != null) {
-                buffer.write(' extends ');
-                buffer.write(bound);
-              }
-            }
-          }
-          buffer.write('>');
-        }
-      } else {
-        buffer.write(parameterName);
-      }
-      buffer.write('(');
-      int count = type.parameters.length;
-      for (int i = 0; i < count; i++) {
-        ParameterElement parameter = type.parameters[i];
-        String parameterType = _getTypeSource(
-            parameter.type, enclosingClass, enclosingExecutable,
-            parameterName: parameter.name,
-            methodBeingCopied: methodBeingCopied);
-        if (i > 0) {
-          buffer.write(', ');
-        }
-        buffer.write(parameterType);
-      }
-      buffer.write(')');
-      if (element is GenericFunctionTypeElement) {
-        buffer.write(' ');
-        buffer.write(parameterName);
-      }
-      return buffer.toString();
-    }
-    // prepare element
-    if (element == null) {
-      String source = type.toString();
-      source = source.replaceAll('<dynamic>', '');
-      source = source.replaceAll('<dynamic, dynamic>', '');
-      if (parameterName != null) {
-        return '$source $parameterName';
-      }
-      return source;
-    }
-    // check if imported
-    StringBuffer buffer = new StringBuffer();
-    LibraryElement definingLibrary = element.library;
-    LibraryElement importingLibrary = dartFileEditBuilder.unit.element.library;
-    if (definingLibrary != null && definingLibrary != importingLibrary) {
-      // no source, if private
-      if (element.isPrivate) {
-        if (parameterName != null) {
-          return parameterName;
-        }
-        return '';
-      }
-      // ensure import
-      ImportElement importElement =
-          _getImportElement(element, importingLibrary);
-      if (importElement != null) {
-        if (importElement.prefix != null) {
-          buffer.write(importElement.prefix.displayName);
-          buffer.write(".");
-        }
-      } else {
-        _LibraryToImport import = _importLibrary(definingLibrary.source.uri);
-        if (import.prefix != null) {
-          buffer.write(import.prefix);
-          buffer.write('.');
-        }
-      }
-    }
-    // append simple name
-    String name = element.displayName;
-    buffer.write(name);
-    // may be type arguments
-    if (type is ParameterizedType) {
-      List<DartType> arguments = type.typeArguments;
-      // check if has arguments
-      bool hasArguments = false;
-      bool allArgumentsVisible = true;
-      for (DartType argument in arguments) {
-        hasArguments = hasArguments || !argument.isDynamic;
-        allArgumentsVisible = allArgumentsVisible &&
-            _getVisibleType(argument, enclosingClass, enclosingExecutable,
-                    methodBeingCopied: methodBeingCopied) !=
-                null;
-      }
-      // append type arguments
-      if (hasArguments && allArgumentsVisible) {
-        buffer.write("<");
-        for (int i = 0; i < arguments.length; i++) {
-          DartType argument = arguments[i];
-          if (i != 0) {
-            buffer.write(", ");
-          }
-          String argumentSrc = _getTypeSource(
-              argument, enclosingClass, enclosingExecutable,
-              methodBeingCopied: methodBeingCopied);
-          if (argumentSrc != null) {
-            buffer.write(argumentSrc);
-          } else {
-            return null;
-          }
-        }
-        buffer.write(">");
-      }
-    }
-    if (parameterName != null) {
-      buffer.write(' ');
-      buffer.write(parameterName);
-    }
-    // done
-    return buffer.toString();
-  }
-
-  /**
    * Returns possible names for a variable with the given expected type and
    * expression assigned.
    */
@@ -1197,6 +1006,137 @@
   _LibraryToImport _importLibrary(Uri uri) {
     return dartFileEditBuilder._importLibrary(uri);
   }
+
+  /**
+   * Write the import prefix to reference the [element], if needed.
+   *
+   * The prefix is not needed if there is already an import without prefix
+   * that exports the [element].  If there there are no existing import
+   * that exports the [element], a library that exports the [element] is
+   * scheduled for import, possibly with a prefix.
+   */
+  void _writeLibraryReference(Element element) {
+    LibraryElement importingLibrary = dartFileEditBuilder.unit.element.library;
+    ImportElement importElement = _getImportElement(element, importingLibrary);
+    if (importElement != null) {
+      if (importElement.prefix != null) {
+        write(importElement.prefix.displayName);
+        write('.');
+      }
+    } else {
+      _LibraryToImport import = _importLibrary(element.library.source.uri);
+      if (import.prefix != null) {
+        write(import.prefix);
+        write('.');
+      }
+    }
+  }
+
+  /**
+   * Write the code to reference [type] in this compilation unit.
+   *
+   * If an [enclosingClass] is provided then the reference is being generated
+   * within the class and the type parameters of the class will be considered to
+   * be visible.
+   *
+   * If an [enclosingExecutable] is provided, then the reference is being
+   * generated within the class and the type parameters of the method will be
+   * considered to be visible.
+   *
+   * If a [methodBeingCopied] is provided, then the type parameters of that
+   * method will be duplicated in the copy and will therefore be visible.
+   *
+   * Causes any libraries whose elements are used by the generated code, to be
+   * imported.
+   */
+  bool _writeType(DartType type, ClassElement enclosingClass,
+      ExecutableElement enclosingExecutable,
+      {ExecutableElement methodBeingCopied}) {
+    type = _getVisibleType(type, enclosingClass, enclosingExecutable,
+        methodBeingCopied: methodBeingCopied);
+
+    // If not a useful type, don't write it.
+    if (type == null || type.isDynamic || type.isBottom) {
+      return false;
+    }
+
+    Element element = type.element;
+
+    // No element, e.g. "void".
+    if (element == null) {
+      write(type.displayName);
+      return true;
+    }
+
+    // Typedef(s) are represented as GenericFunctionTypeElement(s).
+    if (element is GenericFunctionTypeElement &&
+        element.typeParameters.isEmpty &&
+        element.enclosingElement is GenericTypeAliasElement) {
+      element = element.enclosingElement;
+    }
+
+    // Just a Function, not FunctionTypeAliasElement.
+    if (type is FunctionType &&
+        element is FunctionTypedElement &&
+        element is! FunctionTypeAliasElement) {
+      if (_writeType(
+        type.returnType,
+        enclosingClass,
+        enclosingExecutable,
+        methodBeingCopied: methodBeingCopied,
+      )) {
+        write(' ');
+      }
+      write('Function');
+      writeTypeParameters(element.typeParameters,
+          methodBeingCopied: methodBeingCopied);
+      writeParameters(type.parameters, methodBeingCopied: methodBeingCopied);
+      return true;
+    }
+
+    // If the element is not local, ensure that it is imported.
+    // TODO(scheglov) Remember importing library in the edit class.
+    // TODO(scheglov) Check for local element in _writeLibraryReference()?
+    LibraryElement definingLibrary = element.library;
+    LibraryElement importingLibrary = dartFileEditBuilder.unit.element.library;
+    if (definingLibrary != null && definingLibrary != importingLibrary) {
+      _writeLibraryReference(element);
+    }
+
+    // Write the simple name.
+    String name = element.displayName;
+    write(name);
+
+    // Write type arguments.
+    if (type is ParameterizedType) {
+      List<DartType> arguments = type.typeArguments;
+      // Check if has arguments.
+      bool hasArguments = false;
+      bool allArgumentsVisible = true;
+      for (DartType argument in arguments) {
+        hasArguments = hasArguments || !argument.isDynamic;
+        allArgumentsVisible = allArgumentsVisible &&
+            _getVisibleType(argument, enclosingClass, enclosingExecutable,
+                    methodBeingCopied: methodBeingCopied) !=
+                null;
+      }
+      // Write type arguments only if they are useful.
+      if (hasArguments && allArgumentsVisible) {
+        write('<');
+        for (int i = 0; i < arguments.length; i++) {
+          DartType argument = arguments[i];
+          if (i != 0) {
+            write(', ');
+          }
+          _writeType(argument, enclosingClass, enclosingExecutable,
+              methodBeingCopied: methodBeingCopied);
+        }
+        write('>');
+      }
+    }
+
+    return true;
+  }
 }
 
 /**
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 82999a6..91e98bf 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
@@ -1734,6 +1734,32 @@
     expect(edit.replacement, equalsIgnoringWhitespace(''));
   }
 
+  test_writeType_function() async {
+    await _assertWriteType('int Function(double a, String b)');
+  }
+
+  @failingTest
+  test_writeType_function_generic() async {
+    // TODO(scheglov) Fails because T/U are considered invisible.
+    await _assertWriteType('T Function<T, U>(T a, U b)');
+  }
+
+  test_writeType_function_noReturnType() async {
+    await _assertWriteType('Function()');
+  }
+
+  test_writeType_function_parameters_named() async {
+    await _assertWriteType('int Function(int a, {int b, int c})');
+  }
+
+  test_writeType_function_parameters_noName() async {
+    await _assertWriteType('int Function(double, String)');
+  }
+
+  test_writeType_function_parameters_positional() async {
+    await _assertWriteType('int Function(int a, [int b, int c])');
+  }
+
   test_writeType_genericType() async {
     String path = provider.convertPath('/test.dart');
     String content = 'class A {} class B<E> {}';
@@ -1806,6 +1832,14 @@
     expect(values, contains('C'));
   }
 
+  test_writeType_interface_typeArguments() async {
+    await _assertWriteType('Map<int, List<String>>');
+  }
+
+  test_writeType_interface_typeArguments_allDynamic() async {
+    await _assertWriteType('Map');
+  }
+
   test_writeType_null() async {
     String path = provider.convertPath('/test.dart');
     String content = 'class A {}';
@@ -1940,6 +1974,15 @@
     expect(edit.replacement, equalsIgnoringWhitespace('A'));
   }
 
+  test_writeType_typedef_typeArguments() async {
+    await _assertWriteType('F<int, String>',
+        declarations: 'typedef void F<T, U>(T t, U u);');
+  }
+
+  test_writeType_void() async {
+    await _assertWriteType('void Function()');
+  }
+
   test_writeTypes_empty() async {
     String path = provider.convertPath('/test.dart');
     String content = 'class A {}';
@@ -2091,6 +2134,23 @@
     }
   }
 
+  Future<void> _assertWriteType(String typeCode, {String declarations}) async {
+    String path = provider.convertPath('/test.dart');
+    String content = (declarations ?? '') + '$typeCode v;';
+    addSource(path, content);
+
+    var f = await _getTopLevelAccessorElement(path, 'v');
+
+    var builder = new DartChangeBuilder(session);
+    await builder.addFileEdit(path, (builder) {
+      builder.addInsertion(content.length - 1, (builder) {
+        builder.writeType(f.returnType);
+      });
+    });
+    SourceEdit edit = getEdit(builder);
+    expect(edit.replacement, typeCode);
+  }
+
   Future<ClassElement> _getClassElement(String path, String name) async {
     UnitElementResult result = await driver.getUnitElement(path);
     return result.element.getType(name);