lambda generation for positional args

Fixes: https://github.com/dart-lang/sdk/issues/40202

(See issue for animation of UX.)

Change-Id: I029a7768b887fedef4873f3fa23171d21df8808f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/132380
Commit-Queue: Phil Quitslund <pquitslund@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/arglist_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/arglist_contributor.dart
index a3fdab1..bfbd3e5 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/arglist_contributor.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/arglist_contributor.dart
@@ -148,7 +148,7 @@
   ) {
     if (type is FunctionType) {
       var indent = getRequestLineIndent(request);
-      var parametersString = _buildClosureParameters(type);
+      var parametersString = buildClosureParameters(type);
 
       var blockBuffer = StringBuffer(parametersString);
       blockBuffer.writeln(' {');
@@ -340,38 +340,6 @@
     return newExpr != null && flutter.isWidgetCreation(newExpr);
   }
 
-  static String _buildClosureParameters(FunctionType type) {
-    var buffer = StringBuffer();
-    buffer.write('(');
-
-    var hasNamed = false;
-    var hasOptionalPositional = false;
-    var parameters = type.parameters;
-    for (var i = 0; i < parameters.length; ++i) {
-      var parameter = parameters[i];
-      if (i != 0) {
-        buffer.write(', ');
-      }
-      if (parameter.isNamed && !hasNamed) {
-        hasNamed = true;
-        buffer.write('{');
-      } else if (parameter.isOptionalPositional && !hasOptionalPositional) {
-        hasOptionalPositional = true;
-        buffer.write('[');
-      }
-      buffer.write(parameter.name);
-    }
-
-    if (hasNamed) {
-      buffer.write('}');
-    } else if (hasOptionalPositional) {
-      buffer.write(']');
-    }
-
-    buffer.write(')');
-    return buffer.toString();
-  }
-
   /**
    * If the given [comment] is not `null`, fill the [suggestion] documentation
    * fields.
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/utilities.dart b/pkg/analysis_server/lib/src/services/completion/dart/utilities.dart
index b2140d3..86147e9 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/utilities.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/utilities.dart
@@ -60,9 +60,43 @@
       sb.write(', ');
     }
     offset = sb.length;
-    String name = param.name;
-    sb.write(name);
-    ranges.addAll([offset, name.length]);
+
+    if (param.type is FunctionType) {
+      FunctionType type = param.type;
+
+      var rangeStart = offset;
+      var rangeLength;
+
+      // todo (pq): consider adding ranges for params
+      // pending: https://github.com/dart-lang/sdk/issues/40207
+      // (types in closure param completions make this UX awkward)
+      final parametersString = buildClosureParameters(type);
+      final blockBuffer = StringBuffer(parametersString);
+
+      blockBuffer.write(' ');
+
+      // todo (pq): consider refactoring to share common logic w/ ArgListContributor.buildClosureSuggestions
+      final returnType = type.returnType;
+      if (returnType.isVoid) {
+        blockBuffer.write('{');
+        rangeStart = sb.length + blockBuffer.length;
+        blockBuffer.write(' }');
+        rangeLength = 1;
+      } else {
+        final returnValue = returnType.isDartCoreBool ? 'false' : 'null';
+        blockBuffer.write('=> ');
+        rangeStart = sb.length + blockBuffer.length;
+        blockBuffer.write(returnValue);
+        rangeLength = returnValue.length;
+      }
+
+      sb.write(blockBuffer);
+      ranges.addAll([rangeStart, rangeLength]);
+    } else {
+      String name = param.name;
+      sb.write(name);
+      ranges.addAll([offset, name.length]);
+    }
   }
 
   for (ParameterElement param in namedParams) {
@@ -83,6 +117,39 @@
   suggestion.defaultArgumentListTextRanges = ranges.isNotEmpty ? ranges : null;
 }
 
+String buildClosureParameters(FunctionType type) {
+  var buffer = StringBuffer();
+  buffer.write('(');
+
+  var hasNamed = false;
+  var hasOptionalPositional = false;
+  var parameters = type.parameters;
+  for (var i = 0; i < parameters.length; ++i) {
+    var parameter = parameters[i];
+    if (i != 0) {
+      buffer.write(', ');
+    }
+    if (parameter.isNamed && !hasNamed) {
+      hasNamed = true;
+      buffer.write('{');
+    } else if (parameter.isOptionalPositional && !hasOptionalPositional) {
+      hasOptionalPositional = true;
+      buffer.write('[');
+    }
+    // todo (pq): consider abbreviating names
+    buffer.write(parameter.name);
+  }
+
+  if (hasNamed) {
+    buffer.write('}');
+  } else if (hasOptionalPositional) {
+    buffer.write(']');
+  }
+
+  buffer.write(')');
+  return buffer.toString();
+}
+
 /**
  * Create a new protocol Element for inclusion in a completion suggestion.
  */
@@ -268,7 +335,7 @@
   return type.toString();
 }
 
-// TODO(pq): fix to use getDefaultStringParameterValue()
+/// TODO(pq): fix to use getDefaultStringParameterValue()
 String _getDefaultValue(ParameterElement param) => 'null';
 
 /**
diff --git a/pkg/analysis_server/test/services/completion/dart/type_member_contributor_test.dart b/pkg/analysis_server/test/services/completion/dart/type_member_contributor_test.dart
index 365f0e6..f53ba6c 100644
--- a/pkg/analysis_server/test/services/completion/dart/type_member_contributor_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/type_member_contributor_test.dart
@@ -2578,6 +2578,86 @@
     assertNotSuggested('T2');
   }
 
+  test_method_parameter_function_in_param_list() async {
+    addTestSource('''
+class C {
+  void f(int x, void Function(int a, int b) closure, int y) {}
+}
+
+void main() {
+  new C().^
+}
+''');
+    await computeSuggestions();
+
+    final suggestion = assertSuggestMethod('f', 'C', 'void');
+    expect(suggestion.defaultArgumentListString, 'x, (a, b) { }, y');
+
+    // Select 'x', ' ', 'y'
+    expect(suggestion.defaultArgumentListTextRanges,
+        containsAllInOrder([0, 1, 11, 1, 15, 1]));
+  }
+
+  test_method_parameter_function_return_bool() async {
+    addTestSource('''
+class C {
+  void f(bool Function(int a, int b) closure) {}
+}
+
+void main() {
+  new C().^
+}
+''');
+    await computeSuggestions();
+
+    final suggestion = assertSuggestMethod('f', 'C', 'void');
+    expect(suggestion.defaultArgumentListString, '(a, b) => false');
+
+    // Select 'false'
+    expect(
+        suggestion.defaultArgumentListTextRanges, containsAllInOrder([10, 5]));
+  }
+
+  test_method_parameter_function_return_object() async {
+    addTestSource('''
+class C {
+  void f(Object Function(int a, int b) closure) {}
+}
+
+void main() {
+  new C().^
+}
+''');
+    await computeSuggestions();
+
+    final suggestion = assertSuggestMethod('f', 'C', 'void');
+    expect(suggestion.defaultArgumentListString, '(a, b) => null');
+
+    // Select 'null'
+    expect(
+        suggestion.defaultArgumentListTextRanges, containsAllInOrder([10, 4]));
+  }
+
+  test_method_parameter_function_return_void() async {
+    addTestSource('''
+class C {
+  void f(void Function(int a, int b) closure) {}
+}
+
+void main() {
+  new C().^
+}
+''');
+    await computeSuggestions();
+
+    final suggestion = assertSuggestMethod('f', 'C', 'void');
+    expect(suggestion.defaultArgumentListString, '(a, b) { }');
+
+    // Select ' '
+    expect(
+        suggestion.defaultArgumentListTextRanges, containsAllInOrder([8, 1]));
+  }
+
   test_method_parameters_mixed_required_and_named() async {
     addTestSource('''
 class C {