Issue 21965. Add Quick Fix for adding missing named parameter.

R=brianwilkerson@google.com

Bug: https://github.com/dart-lang/sdk/issues/21965
Change-Id: I81a39df6fd38f9f562b494483b3fe0f67f9a627b
Reviewed-on: https://dart-review.googlesource.com/54360
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/fix.dart b/pkg/analysis_server/lib/src/services/correction/fix.dart
index 3ba57d8..cdcdc36 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix.dart
@@ -98,6 +98,8 @@
       'ADD_MISSING_PARAMETER_POSITIONAL',
       31,
       "Add optional positional parameter");
+  static const ADD_MISSING_PARAMETER_NAMED = const FixKind(
+      'ADD_MISSING_PARAMETER_NAMED', 30, "Add named parameter '{0}'");
   static const ADD_MISSING_PARAMETER_REQUIRED = const FixKind(
       'ADD_MISSING_PARAMETER_REQUIRED', 30, "Add required parameter");
   static const ADD_MISSING_REQUIRED_ARGUMENT = const FixKind(
diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
index 5453c62..79b6f55 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -361,6 +361,9 @@
       await _addFix_importLibrary_withTopLevelVariable();
       await _addFix_createLocalVariable();
     }
+    if (errorCode == StaticWarningCode.UNDEFINED_NAMED_PARAMETER) {
+      await _addFix_addMissingNamedArgument();
+    }
     if (errorCode == StaticTypeWarningCode.UNDEFINED_METHOD_WITH_CONSTRUCTOR) {
       await _addFix_undefinedMethodWithContructor();
     }
@@ -608,6 +611,60 @@
     }
   }
 
+  Future<Null> _addFix_addMissingNamedArgument() async {
+    // Prepare the name of the missing parameter.
+    if (this.node is! SimpleIdentifier) {
+      return;
+    }
+    SimpleIdentifier node = this.node;
+    String name = node.name;
+
+    // We expect that the node is part of a NamedExpression.
+    if (node.parent?.parent is! NamedExpression) {
+      return;
+    }
+    NamedExpression namedExpression = node.parent.parent;
+
+    // We should be in an ArgumentList.
+    if (namedExpression.parent is! ArgumentList) {
+      return;
+    }
+    AstNode argumentList = namedExpression.parent;
+
+    // Prepare the invoked element.
+    var context =
+        new _ExecutableParameters(session, astProvider, argumentList.parent);
+    if (context == null) {
+      return;
+    }
+
+    // We cannot add named parameters when there are positional positional.
+    if (context.optionalPositional.isNotEmpty) {
+      return;
+    }
+
+    Future<void> addParameter(int offset, String prefix, String suffix) async {
+      if (offset != null) {
+        DartChangeBuilder changeBuilder = await context.addParameter(
+            offset, prefix, namedExpression.staticType, name, suffix);
+        _addFixFromBuilder(
+            changeBuilder, DartFixKind.ADD_MISSING_PARAMETER_NAMED,
+            args: [name]);
+      }
+    }
+
+    if (context.named.isNotEmpty) {
+      var prevNode = await context.getParameterNode(context.named.last);
+      await addParameter(prevNode?.end, ', ', '');
+    } else if (context.required.isNotEmpty) {
+      var prevNode = await context.getParameterNode(context.required.last);
+      await addParameter(prevNode?.end, ', {', '}');
+    } else {
+      var parameterList = await context.getParameterList();
+      await addParameter(parameterList?.leftParenthesis?.end, '{', '}');
+    }
+  }
+
   Future<Null> _addFix_addMissingParameter() async {
     if (node is ArgumentList && node.parent is MethodInvocation) {
       ArgumentList argumentList = node;
@@ -3729,3 +3786,91 @@
     }
   }
 }
+
+/**
+ * [ExecutableElement], its parameters, and operations on them.
+ */
+class _ExecutableParameters {
+  final AnalysisSession session;
+  final AstProvider astProvider;
+  final ExecutableElement executable;
+
+  final List<ParameterElement> required = [];
+  final List<ParameterElement> optionalPositional = [];
+  final List<ParameterElement> named = [];
+
+  factory _ExecutableParameters(
+      AnalysisSession session, AstProvider astProvider, AstNode invocation) {
+    Element element;
+    if (invocation is InstanceCreationExpression) {
+      element = invocation.staticElement;
+    }
+    if (invocation is MethodInvocation) {
+      element = invocation.methodName.staticElement;
+    }
+    if (element is ExecutableElement) {
+      return new _ExecutableParameters._(session, astProvider, element);
+    } else {
+      return null;
+    }
+  }
+
+  _ExecutableParameters._(this.session, this.astProvider, this.executable) {
+    for (var parameter in executable.parameters) {
+      if (parameter.isNotOptional) {
+        required.add(parameter);
+      } else if (parameter.isOptionalPositional) {
+        optionalPositional.add(parameter);
+      } else if (parameter.isNamed) {
+        named.add(parameter);
+      }
+    }
+  }
+
+  /**
+   * Write the code for a new parameter with the given [type] and [name].
+   */
+  Future<DartChangeBuilder> addParameter(int offset, String prefix,
+      DartType type, String name, String suffix) async {
+    String targetFile = executable.source.fullName;
+    var changeBuilder = new DartChangeBuilder(session);
+    await changeBuilder.addFileEdit(targetFile, (builder) {
+      builder.addInsertion(offset, (builder) {
+        builder.write(prefix);
+        builder.writeParameter(name, type: type);
+        builder.write(suffix);
+      });
+    });
+    return changeBuilder;
+  }
+
+  /**
+   * Return the [FormalParameterList] of the [executable], or `null` is cannot
+   * be found.
+   */
+  Future<FormalParameterList> getParameterList() async {
+    var name = await astProvider.getParsedNameForElement(executable);
+    AstNode targetDeclaration = name?.parent;
+    if (targetDeclaration is FunctionDeclaration) {
+      FunctionExpression function = targetDeclaration.functionExpression;
+      return function.parameters;
+    } else if (targetDeclaration is MethodDeclaration) {
+      return targetDeclaration.parameters;
+    }
+    return null;
+  }
+
+  /**
+   * Return the [FormalParameter] of the [element] in [FormalParameterList],
+   * or `null` is cannot be found.
+   */
+  Future<FormalParameter> getParameterNode(ParameterElement element) async {
+    var name = await astProvider.getParsedNameForElement(element);
+    for (AstNode node = name; node != null; node = node.parent) {
+      if (node is FormalParameter && node.parent is FormalParameterList) {
+        return node;
+      }
+    }
+    return null;
+  }
+}
diff --git a/pkg/analysis_server/test/services/correction/fix_test.dart b/pkg/analysis_server/test/services/correction/fix_test.dart
index a95b938..0f0bb9c 100644
--- a/pkg/analysis_server/test/services/correction/fix_test.dart
+++ b/pkg/analysis_server/test/services/correction/fix_test.dart
@@ -825,6 +825,120 @@
 ''');
   }
 
+  test_addMissingParameterNamed_function_hasNamed() async {
+    await resolveTestUnit('''
+test(int a, {int b: 0}) {}
+
+main() {
+  test(1, b: 2, named: 3.0);
+}
+''');
+    await assertHasFix(DartFixKind.ADD_MISSING_PARAMETER_NAMED, '''
+test(int a, {int b: 0, double named}) {}
+
+main() {
+  test(1, b: 2, named: 3.0);
+}
+''');
+  }
+
+  test_addMissingParameterNamed_function_hasRequired() async {
+    await resolveTestUnit('''
+test(int a) {}
+
+main() {
+  test(1, named: 2.0);
+}
+''');
+    await assertHasFix(DartFixKind.ADD_MISSING_PARAMETER_NAMED, '''
+test(int a, {double named}) {}
+
+main() {
+  test(1, named: 2.0);
+}
+''');
+  }
+
+  test_addMissingParameterNamed_function_noParameters() async {
+    await resolveTestUnit('''
+test() {}
+
+main() {
+  test(named: 42);
+}
+''');
+    await assertHasFix(DartFixKind.ADD_MISSING_PARAMETER_NAMED, '''
+test({int named}) {}
+
+main() {
+  test(named: 42);
+}
+''');
+  }
+
+  test_addMissingParameterNamed_method_hasNamed() async {
+    await resolveTestUnit('''
+class A {
+  test(int a, {int b: 0}) {}
+
+  main() {
+    test(1, b: 2, named: 3.0);
+  }
+}
+''');
+    await assertHasFix(DartFixKind.ADD_MISSING_PARAMETER_NAMED, '''
+class A {
+  test(int a, {int b: 0, double named}) {}
+
+  main() {
+    test(1, b: 2, named: 3.0);
+  }
+}
+''');
+  }
+
+  test_addMissingParameterNamed_method_hasRequired() async {
+    await resolveTestUnit('''
+class A {
+  test(int a) {}
+
+  main() {
+    test(1, named: 2.0);
+  }
+}
+''');
+    await assertHasFix(DartFixKind.ADD_MISSING_PARAMETER_NAMED, '''
+class A {
+  test(int a, {double named}) {}
+
+  main() {
+    test(1, named: 2.0);
+  }
+}
+''');
+  }
+
+  test_addMissingParameterNamed_method_noParameters() async {
+    await resolveTestUnit('''
+class A {
+  test() {}
+
+  main() {
+    test(named: 42);
+  }
+}
+''');
+    await assertHasFix(DartFixKind.ADD_MISSING_PARAMETER_NAMED, '''
+class A {
+  test({int named}) {}
+
+  main() {
+    test(named: 42);
+  }
+}
+''');
+  }
+
   test_addMissingRequiredArg_cons_flutter_children() async {
     addFlutterPackage();
     _addMetaPackageSource();