Version 2.16.0-97.0.dev

Merge commit 'b0977940fa38a25443d3ccebb7c53bd327246a83' into 'dev'
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 21f5f37..8cdcb87 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
@@ -9,9 +9,7 @@
 import 'package:analysis_server/src/utilities/flutter.dart';
 import 'package:analyzer/dart/analysis/features.dart';
 import 'package:analyzer/dart/ast/ast.dart';
-import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/dart/element/element.dart';
-import 'package:analyzer/dart/element/type.dart';
 
 /// A contributor that produces suggestions for named expression labels that
 /// correspond to named parameters when completing in argument lists.
@@ -113,7 +111,8 @@
                 ? 0
                 : null;
 
-        var addTrailingComma = !_isFollowedByAComma() && _isInFlutterCreation();
+        var addTrailingComma =
+            !request.target.isFollowedByComma && _isInFlutterCreation();
         _addDefaultParamSuggestions(parameters,
             appendComma: addTrailingComma,
             replacementLength: replacementLength);
@@ -122,12 +121,7 @@
       _addDefaultParamSuggestions(parameters, appendComma: true);
     } else if (_isInsertingToArgListWithSynthetic()) {
       _addDefaultParamSuggestions(parameters,
-          appendComma: !_isFollowedByAComma());
-    } else {
-      var argument = request.target.containingNode;
-      if (argument is NamedExpression) {
-        _buildClosureSuggestions(argument);
-      }
+          appendComma: !request.target.isFollowedByComma);
     }
   }
 
@@ -147,15 +141,6 @@
     return 0;
   }
 
-  void _buildClosureSuggestions(NamedExpression argument) {
-    var type = argument.staticParameterElement?.type;
-    if (type is FunctionType) {
-      builder.suggestClosure(type,
-          includeTrailingComma:
-              argument.endToken.next?.type != TokenType.COMMA);
-    }
-  }
-
   /// Return `true` if the caret is preceding an arg where a name could be added
   /// (turning a positional arg into a named arg).
   bool _isAddingLabelToPositional() {
@@ -222,24 +207,6 @@
     return false;
   }
 
-  bool _isFollowedByAComma() {
-    // new A(^); NO
-    // new A(one: 1, ^); NO
-    // new A(^ , one: 1); YES
-    // new A(^), ... NO
-
-    var containingNode = request.target.containingNode;
-    var entity = request.target.entity;
-    var token = entity is AstNode
-        ? entity.endToken
-        : entity is Token
-            ? entity
-            : null;
-    return (token != containingNode.endToken) &&
-        token?.next?.type == TokenType.COMMA &&
-        !(token?.next?.isSynthetic ?? false);
-  }
-
   bool _isInFlutterCreation() {
     var flutter = Flutter.instance;
     var containingNode = request.target.containingNode;
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/closure_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/closure_contributor.dart
new file mode 100644
index 0000000..5700872
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/completion/dart/closure_contributor.dart
@@ -0,0 +1,33 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// 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/provisional/completion/dart/completion_dart.dart';
+import 'package:analysis_server/src/services/completion/dart/completion_manager.dart';
+import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/element/type.dart';
+
+/// A contributor that produces a closure matching the context type.
+class ClosureContributor extends DartCompletionContributor {
+  ClosureContributor(
+    DartCompletionRequest request,
+    SuggestionBuilder builder,
+  ) : super(request, builder);
+
+  bool get _isArgument {
+    var node = request.target.containingNode;
+    return node is ArgumentList || node is NamedExpression;
+  }
+
+  @override
+  Future<void> computeSuggestions() async {
+    var contextType = request.contextType;
+    if (contextType is FunctionType) {
+      builder.suggestClosure(
+        contextType,
+        includeTrailingComma: _isArgument && !request.target.isFollowedByComma,
+      );
+    }
+  }
+}
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart b/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart
index 90da0ef..4193128 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart
@@ -6,6 +6,7 @@
 import 'package:analysis_server/src/provisional/completion/completion_core.dart';
 import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
 import 'package:analysis_server/src/services/completion/dart/arglist_contributor.dart';
+import 'package:analysis_server/src/services/completion/dart/closure_contributor.dart';
 import 'package:analysis_server/src/services/completion/dart/combinator_contributor.dart';
 import 'package:analysis_server/src/services/completion/dart/documentation_cache.dart';
 import 'package:analysis_server/src/services/completion/dart/extension_member_contributor.dart';
@@ -138,6 +139,7 @@
     var builder = SuggestionBuilder(request, listener: listener);
     var contributors = <DartCompletionContributor>[
       ArgListContributor(request, builder),
+      ClosureContributor(request, builder),
       CombinatorContributor(request, builder),
       ExtensionMemberContributor(request, builder),
       FieldFormalContributor(request, builder),
diff --git a/pkg/analysis_server/test/services/completion/dart/arglist_contributor_test.dart b/pkg/analysis_server/test/services/completion/dart/arglist_contributor_test.dart
index fc2016f..f320c5f 100644
--- a/pkg/analysis_server/test/services/completion/dart/arglist_contributor_test.dart
+++ b/pkg/analysis_server/test/services/completion/dart/arglist_contributor_test.dart
@@ -120,107 +120,6 @@
 @reflectiveTest
 class ArgListContributorTest extends DartCompletionContributorTest
     with ArgListContributorMixin {
-  Future<void> test_closure_namedArgument() async {
-    addTestSource(r'''
-void f({void Function(int a, String b) closure}) {}
-
-void main() {
-  f(closure: ^);
-}
-''');
-    await computeSuggestions();
-
-    assertSuggest(
-      '(a, b) => ,',
-      selectionOffset: 10,
-    );
-
-    assertSuggest(
-      '''
-(a, b) {
-${' ' * 4}
-${' ' * 2}},''',
-      selectionOffset: 13,
-    );
-  }
-
-  Future<void> test_closure_namedArgument_hasComma() async {
-    addTestSource(r'''
-void f({void Function(int a, String b) closure}) {}
-
-void main() {
-  f(
-    closure: ^,
-  );
-}
-''');
-    await computeSuggestions();
-
-    assertSuggest(
-      '(a, b) => ',
-      selectionOffset: 10,
-    );
-
-    assertSuggest(
-      '''
-(a, b) {
-${' ' * 6}
-${' ' * 4}}''',
-      selectionOffset: 15,
-    );
-  }
-
-  Future<void> test_closure_namedArgument_parameters_optionalNamed() async {
-    addTestSource(r'''
-void f({void Function(int a, {int b, int c}) closure}) {}
-
-void main() {
-  f(closure: ^);
-}
-''');
-    await computeSuggestions();
-
-    assertSuggest(
-      '(a, {b, c}) => ,',
-      selectionOffset: 15,
-    );
-  }
-
-  Future<void>
-      test_closure_namedArgument_parameters_optionalPositional() async {
-    addTestSource(r'''
-void f({void Function(int a, [int b, int c]) closure]) {}
-
-void main() {
-  f(closure: ^);
-}
-''');
-    await computeSuggestions();
-
-    assertSuggest(
-      '(a, [b, c]) => ,',
-      selectionOffset: 15,
-    );
-  }
-
-  /// todo (pq): implement positional functional parameters
-  @failingTest
-  Future<void> test_closure_positionalArgument() async {
-    addTestSource(r'''
-void f(void Function(int a, int b) closure) {}
-
-void main() {
-  f(^);
-}
-''');
-    await computeSuggestions();
-
-    assertSuggest(
-      '(a, b, c) => ,',
-      selectionOffset: 13,
-    );
-  }
-
   Future<void> test_fieldFormal_documentation() async {
     var content = '''
 class A {
@@ -486,6 +385,45 @@
   Future<void> test_named_03() async {
     await _tryParametersArguments(
       parameters: '({bool one, int two})',
+      arguments: '(o^ two: 2)',
+      check: () {
+        assertSuggestArgumentsAndTypes(
+            namedArgumentsWithTypes: {'one': 'bool'}, includeComma: true);
+        assertSuggestArgumentAndCompletion('one',
+            completion: 'one: ,', selectionOffset: 5);
+      },
+    );
+  }
+
+  Future<void> test_named_04() async {
+    await _tryParametersArguments(
+      parameters: '({bool one, int two})',
+      arguments: '(o^, two: 2)',
+      check: () {
+        assertSuggestArgumentsAndTypes(
+            namedArgumentsWithTypes: {'one': 'bool'}, includeComma: false);
+        assertSuggestArgumentAndCompletion('one',
+            completion: 'one: ', selectionOffset: 5);
+      },
+    );
+  }
+
+  Future<void> test_named_05() async {
+    await _tryParametersArguments(
+      parameters: '({bool one, int two})',
+      arguments: '(o^ , two: 2)',
+      check: () {
+        assertSuggestArgumentsAndTypes(
+            namedArgumentsWithTypes: {'one': 'bool'}, includeComma: false);
+        assertSuggestArgumentAndCompletion('one',
+            completion: 'one: ', selectionOffset: 5);
+      },
+    );
+  }
+
+  Future<void> test_named_06() async {
+    await _tryParametersArguments(
+      parameters: '({bool one, int two})',
       arguments: '(^o,)',
       check: () {
         assertSuggestArgumentsAndTypes(
@@ -497,7 +435,7 @@
     );
   }
 
-  Future<void> test_named_04() async {
+  Future<void> test_named_07() async {
     await _tryParametersArguments(
       parameters: '({bool one, int two})',
       arguments: '(^ two: 2)',
@@ -510,20 +448,7 @@
     );
   }
 
-  Future<void> test_named_05() async {
-    await _tryParametersArguments(
-      parameters: '({bool one, int two})',
-      arguments: '(o^ two: 2)',
-      check: () {
-        assertSuggestArgumentsAndTypes(
-            namedArgumentsWithTypes: {'one': 'bool'}, includeComma: true);
-        assertSuggestArgumentAndCompletion('one',
-            completion: 'one: ,', selectionOffset: 5);
-      },
-    );
-  }
-
-  Future<void> test_named_06() async {
+  Future<void> test_named_08() async {
     await _tryParametersArguments(
       parameters: '({bool one, int two})',
       arguments: '(^two: 2)',
@@ -533,7 +458,7 @@
     );
   }
 
-  Future<void> test_named_07() async {
+  Future<void> test_named_09() async {
     await _tryParametersArguments(
       parameters: '({bool one, int two})',
       arguments: '(^, two: 2)',
@@ -548,20 +473,7 @@
     );
   }
 
-  Future<void> test_named_08() async {
-    await _tryParametersArguments(
-      parameters: '({bool one, int two})',
-      arguments: '(o^, two: 2)',
-      check: () {
-        assertSuggestArgumentsAndTypes(
-            namedArgumentsWithTypes: {'one': 'bool'}, includeComma: false);
-        assertSuggestArgumentAndCompletion('one',
-            completion: 'one: ', selectionOffset: 5);
-      },
-    );
-  }
-
-  Future<void> test_named_09() async {
+  Future<void> test_named_10() async {
     await _tryParametersArguments(
       parameters: '({bool one, int two})',
       arguments: '(^ , two: 2)',
@@ -573,7 +485,7 @@
     );
   }
 
-  Future<void> test_named_10() async {
+  Future<void> test_named_11() async {
     await _tryParametersArguments(
       parameters: '(int one, {bool two, int three})',
       arguments: '(1, ^, three: 3)',
@@ -585,7 +497,7 @@
     );
   }
 
-  Future<void> test_named_11() async {
+  Future<void> test_named_12() async {
     await _tryParametersArguments(
       parameters: '(int one, {bool two, int three})',
       arguments: '(1, ^ three: 3)',
@@ -595,7 +507,7 @@
     );
   }
 
-  Future<void> test_named_12() async {
+  Future<void> test_named_13() async {
     await _tryParametersArguments(
       parameters: '(int one, {bool two, int three})',
       arguments: '(1, ^three: 3)',
@@ -606,7 +518,7 @@
   }
 
   @failingTest
-  Future<void> test_named_13() async {
+  Future<void> test_named_14() async {
     await _tryParametersArguments(
       parameters: '({bool one, int two})',
       arguments: '(two: 2^)',
@@ -617,7 +529,7 @@
   }
 
   @failingTest
-  Future<void> test_named_14() async {
+  Future<void> test_named_15() async {
     await _tryParametersArguments(
       parameters: '({bool one, int two})',
       arguments: '(two: 2 ^)',
@@ -627,7 +539,7 @@
     );
   }
 
-  Future<void> test_named_15() async {
+  Future<void> test_named_16() async {
     await _tryParametersArguments(
       parameters: '({bool one, int two})',
       arguments: '(two: 2, ^)',
@@ -641,7 +553,7 @@
     );
   }
 
-  Future<void> test_named_16() async {
+  Future<void> test_named_17() async {
     await _tryParametersArguments(
       parameters: '({bool one, int two})',
       arguments: '(two: 2, o^)',
@@ -655,7 +567,7 @@
     );
   }
 
-  Future<void> test_named_17() async {
+  Future<void> test_named_18() async {
     await _tryParametersArguments(
       parameters: '({bool one, int two})',
       arguments: '(two: 2, o^,)',
@@ -669,7 +581,7 @@
     );
   }
 
-  Future<void> test_named_18() async {
+  Future<void> test_named_19() async {
     await _tryParametersArguments(
       parameters: '(int one, int two, int three, {int four, int five})',
       arguments: '(1, ^, 3)',
@@ -679,7 +591,7 @@
     );
   }
 
-  Future<void> test_named_19() async {
+  Future<void> test_named_20() async {
     await _tryParametersArguments(
       languageVersion: '2.15',
       parameters: '(int one, int two, int three, {int four, int five})',
@@ -690,7 +602,7 @@
     );
   }
 
-  Future<void> test_named_20() async {
+  Future<void> test_named_21() async {
     await _tryParametersArguments(
       parameters: '({bool one, int two})',
       arguments: '(o^: false)',
@@ -703,7 +615,7 @@
     );
   }
 
-  Future<void> test_named_21() async {
+  Future<void> test_named_22() async {
     await _tryParametersArguments(
       parameters: '(bool one, {int two, double three})',
       arguments: '(false, ^t: 2)',
@@ -713,7 +625,7 @@
     );
   }
 
-  Future<void> test_named_22() async {
+  Future<void> test_named_23() async {
     await _tryParametersArguments(
       parameters: '(bool one, {int two, double three})',
       arguments: '(false, ^: 2)',
@@ -725,7 +637,7 @@
     );
   }
 
-  Future<void> test_named_23() async {
+  Future<void> test_named_24() async {
     await _tryParametersArguments(
       parameters: '({bool one, int two})',
       arguments: '(one: ^)',
diff --git a/pkg/analysis_server/test/services/completion/dart/closure_contributor_test.dart b/pkg/analysis_server/test/services/completion/dart/closure_contributor_test.dart
new file mode 100644
index 0000000..21a2ef7
--- /dev/null
+++ b/pkg/analysis_server/test/services/completion/dart/closure_contributor_test.dart
@@ -0,0 +1,154 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// 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/provisional/completion/dart/completion_dart.dart';
+import 'package:analysis_server/src/services/completion/dart/closure_contributor.dart';
+import 'package:analysis_server/src/services/completion/dart/completion_manager.dart';
+import 'package:analysis_server/src/services/completion/dart/suggestion_builder.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'completion_contributor_util.dart';
+
+void main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(ClosureContributorTest);
+  });
+}
+
+@reflectiveTest
+class ClosureContributorTest extends DartCompletionContributorTest {
+  @override
+  DartCompletionContributor createContributor(
+    DartCompletionRequest request,
+    SuggestionBuilder builder,
+  ) {
+    return ClosureContributor(request, builder);
+  }
+
+  Future<void> test_argumentList_named() async {
+    addTestSource(r'''
+void f({void Function(int a, String b) closure}) {}
+
+void main() {
+  f(closure: ^);
+}
+''');
+    await computeSuggestions();
+
+    assertSuggest(
+      '(a, b) => ,',
+      selectionOffset: 10,
+    );
+
+    assertSuggest(
+      '''
+(a, b) {
+${' ' * 4}
+${' ' * 2}},''',
+      selectionOffset: 13,
+    );
+  }
+
+  Future<void> test_argumentList_named_hasComma() async {
+    addTestSource(r'''
+void f({void Function(int a, String b) closure}) {}
+
+void main() {
+  f(
+    closure: ^,
+  );
+}
+''');
+    await computeSuggestions();
+
+    assertSuggest(
+      '(a, b) => ',
+      selectionOffset: 10,
+    );
+
+    assertSuggest(
+      '''
+(a, b) {
+${' ' * 6}
+${' ' * 4}}''',
+      selectionOffset: 15,
+    );
+  }
+
+  Future<void> test_argumentList_positional() async {
+    addTestSource(r'''
+void f(void Function(int a, int b) closure) {}
+
+void main() {
+  f(^);
+}
+''');
+    await computeSuggestions();
+
+    assertSuggest(
+      '(a, b) => ,',
+      selectionOffset: 10,
+    );
+  }
+
+  Future<void> test_argumentList_positional_hasComma() async {
+    addTestSource(r'''
+void f(void Function(int a, int b) closure) {}
+
+void main() {
+  f(^,);
+}
+''');
+    await computeSuggestions();
+
+    assertSuggest(
+      '(a, b) => ',
+      selectionOffset: 10,
+    );
+  }
+
+  Future<void> test_parameters_optionalNamed() async {
+    addTestSource(r'''
+void f({void Function(int a, {int b, int c}) closure}) {}
+
+void main() {
+  f(closure: ^);
+}
+''');
+    await computeSuggestions();
+
+    assertSuggest(
+      '(a, {b, c}) => ,',
+      selectionOffset: 15,
+    );
+  }
+
+  Future<void> test_parameters_optionalPositional() async {
+    addTestSource(r'''
+void f({void Function(int a, [int b, int c]) closure]) {}
+
+void main() {
+  f(closure: ^);
+}
+''');
+    await computeSuggestions();
+
+    assertSuggest(
+      '(a, [b, c]) => ,',
+      selectionOffset: 15,
+    );
+  }
+
+  Future<void> test_variableInitializer() async {
+    addTestSource(r'''
+void Function(int a, int b) v = ^;
+''');
+    await computeSuggestions();
+
+    assertSuggest(
+      '(a, b) => ',
+      selectionOffset: 10,
+    );
+  }
+}
diff --git a/pkg/analysis_server/test/services/completion/dart/test_all.dart b/pkg/analysis_server/test/services/completion/dart/test_all.dart
index c8ccce6..4ed7cd0 100644
--- a/pkg/analysis_server/test/services/completion/dart/test_all.dart
+++ b/pkg/analysis_server/test/services/completion/dart/test_all.dart
@@ -5,6 +5,7 @@
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import 'arglist_contributor_test.dart' as arglist_test;
+import 'closure_contributor_test.dart' as closure_contributor;
 import 'combinator_contributor_test.dart' as combinator_test;
 import 'completion_manager_test.dart' as completion_manager;
 import 'extension_member_contributor_test.dart' as extension_member_contributor;
@@ -27,6 +28,7 @@
 void main() {
   defineReflectiveSuite(() {
     arglist_test.main();
+    closure_contributor.main();
     combinator_test.main();
     completion_manager.main();
     extension_member_contributor.main();
diff --git a/pkg/analyzer_plugin/lib/src/utilities/completion/completion_target.dart b/pkg/analyzer_plugin/lib/src/utilities/completion/completion_target.dart
index 24e9359..31590d0 100644
--- a/pkg/analyzer_plugin/lib/src/utilities/completion/completion_target.dart
+++ b/pkg/analyzer_plugin/lib/src/utilities/completion/completion_target.dart
@@ -343,6 +343,37 @@
     return false;
   }
 
+  /// Return `true` if the [offset] is followed by a comma.
+  bool get isFollowedByComma {
+    // f(^); NO
+    // f(one: 1, ^); NO
+    // f(^ , one: 1); YES
+    // f(^, one: 1); YES
+    // f(^ one: 1); NO
+
+    bool isExistingComma(Token? token) {
+      return token != null &&
+          !token.isSynthetic &&
+          token.type == TokenType.COMMA;
+    }
+
+    var entity = this.entity;
+
+    Token token;
+    if (entity is AstNode) {
+      token = entity.endToken;
+    } else if (entity is Token) {
+      token = entity;
+    } else {
+      return false;
+    }
+
+    if (token.offset <= offset && offset <= token.end) {
+      return isExistingComma(token.next);
+    }
+    return isExistingComma(token);
+  }
+
   /// If the target is an argument in an argument list, and the invocation is
   /// resolved, return the corresponding [ParameterElement].
   ParameterElement? get parameterElement {
diff --git a/pkg/test_runner/lib/src/testing_servers.dart b/pkg/test_runner/lib/src/testing_servers.dart
index 984f83c..ca69031 100644
--- a/pkg/test_runner/lib/src/testing_servers.dart
+++ b/pkg/test_runner/lib/src/testing_servers.dart
@@ -166,6 +166,7 @@
       server.addHandler('/$prefixBuildDir', fileHandler);
       server.addHandler('/$prefixDartDir', fileHandler);
       server.addHandler('/packages', fileHandler);
+      server.addHandler('/upload', _handleUploadRequest);
       _serverList.add(httpServer);
       return server;
     });
@@ -237,6 +238,23 @@
     });
   }
 
+  void _handleUploadRequest(HttpRequest request) async {
+    try {
+      var builder = await request.fold(BytesBuilder(), (var b, var d) {
+        b.add(d);
+        return b;
+      });
+      var data = builder.takeBytes();
+      DebugLogger.info(
+          'Uploaded data: ${String.fromCharCodes(data as Iterable<int>)}');
+      request.response.headers.set("Access-Control-Allow-Origin", "*");
+      request.response.close();
+    } catch (e) {
+      DebugLogger.warning(
+          'HttpServer: error while processing upload request', e);
+    }
+  }
+
   Uri _getFileUriFromRequestUri(Uri request) {
     // Go to the top of the file to see an explanation of the URL path scheme.
     var pathSegments = request.normalizePath().pathSegments;
diff --git a/tools/VERSION b/tools/VERSION
index 2ce508d..63f943c 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 16
 PATCH 0
-PRERELEASE 96
+PRERELEASE 97
 PRERELEASE_PATCH 0
\ No newline at end of file