Add fix for creating a parameter

The variant of the parameter, optional/required and named/positional, is inferred from the other parameters used in the method.

Change-Id: I0c26e5d16e759978410ba854c0fed0a75d33724f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/365826
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Moritz Sümmermann <mosum@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/create_parameter.dart b/pkg/analysis_server/lib/src/services/correction/dart/create_parameter.dart
new file mode 100644
index 0000000..56dd9ee
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/correction/dart/create_parameter.dart
@@ -0,0 +1,112 @@
+// Copyright (c) 2024, 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/services/correction/dart/abstract_producer.dart';
+import 'package:analysis_server/src/services/correction/fix.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/element/nullability_suffix.dart';
+import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:analyzer_plugin/utilities/range_factory.dart';
+
+class CreateParameter extends ResolvedCorrectionProducer {
+  String _parameterName = '';
+
+  @override
+  CorrectionApplicability get applicability =>
+      CorrectionApplicability.singleLocation;
+
+  @override
+  List<String> get fixArguments => [_parameterName];
+
+  @override
+  FixKind get fixKind => DartFixKind.CREATE_PARAMETER;
+
+  @override
+  Future<void> compute(ChangeBuilder builder) async {
+    var nameNode = node;
+    if (nameNode is! SimpleIdentifier) {
+      return;
+    }
+    _parameterName = nameNode.name;
+    // prepare target Statement
+    var parameters =
+        node.thisOrAncestorOfType<FunctionExpression>()?.parameters ??
+            node.thisOrAncestorOfType<MethodDeclaration>()?.parameters ??
+            node.thisOrAncestorOfType<ConstructorDeclaration>()?.parameters;
+    if (parameters == null) {
+      return;
+    }
+
+    var requiredPositionals =
+        parameters.parameters.where((p) => p.isRequiredPositional);
+    var namedParameters = parameters.parameters.where((p) => p.isNamed);
+    var somethingAfterPositionals = requiredPositionals.isNotEmpty &&
+        parameters.parameters.any((p) => !p.isRequiredPositional);
+    var somethingBeforeNamed = requiredPositionals.isEmpty &&
+        parameters.parameters.any((p) => !p.isNamed);
+    var hasFollowingParameters =
+        somethingAfterPositionals || somethingBeforeNamed;
+
+    // compute type
+    var type =
+        inferUndefinedExpressionType(nameNode) ?? typeProvider.dynamicType;
+    var lastRequiredPositional = requiredPositionals.lastOrNull;
+    var lastNamed = namedParameters.lastOrNull;
+    var hasPreviousParameters =
+        lastRequiredPositional != null || lastNamed != null;
+    var last = lastRequiredPositional ?? lastNamed;
+    var trailingComma =
+        parameters.parameters.lastOrNull?.endToken.next?.lexeme == ',';
+
+    int insertionToken;
+    if (hasPreviousParameters) {
+      if (trailingComma) {
+        // After comma
+        insertionToken = last!.endToken.next!.end;
+      } else if (hasFollowingParameters) {
+        // After whitespace after comma
+        insertionToken = last!.endToken.next!.end + 1;
+      } else {
+        // After last, as there is no comma
+        insertionToken = last!.end;
+      }
+    } else {
+      // At first position
+      insertionToken = parameters.leftParenthesis.end;
+    }
+
+    await builder.addDartFileEdit(file, (builder) {
+      builder.addInsertion(insertionToken, (builder) {
+        //prefix
+        if (hasPreviousParameters) {
+          if (trailingComma) {
+            builder.writeln();
+            var whitespace = utils.getNodePrefix(last!);
+            builder.write(whitespace);
+          } else if (!hasFollowingParameters) {
+            builder.write(', ');
+          }
+        }
+        builder.writeParameter(
+          _parameterName,
+          type: type,
+          nameGroupName: 'NAME',
+          typeGroupName: 'TYPE',
+          isRequiredType: true,
+          isRequiredNamed: last != null &&
+              last == lastNamed &&
+              type.nullabilitySuffix != NullabilitySuffix.question,
+        );
+        //suffix
+        if (trailingComma) {
+          builder.write(',');
+        } else if (hasFollowingParameters) {
+          builder.write(', ');
+        }
+      });
+      builder.addLinkedPosition(range.node(node), 'NAME');
+    });
+  }
+}
diff --git a/pkg/analysis_server/lib/src/services/correction/fix.dart b/pkg/analysis_server/lib/src/services/correction/fix.dart
index 6d98b87..f0c2d69 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix.dart
@@ -723,6 +723,11 @@
     49,
     "Create 'noSuchMethod' method",
   );
+  static const CREATE_PARAMETER = FixKind(
+    'dart.fix.create.parameter',
+    DartFixKindPriority.DEFAULT,
+    "Create required positional parameter '{0}'",
+  );
   static const CREATE_SETTER = FixKind(
     'dart.fix.create.setter',
     DartFixKindPriority.DEFAULT,
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 38a4b32..9ef02b0 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -91,6 +91,7 @@
 import 'package:analysis_server/src/services/correction/dart/create_missing_overrides.dart';
 import 'package:analysis_server/src/services/correction/dart/create_mixin.dart';
 import 'package:analysis_server/src/services/correction/dart/create_no_such_method.dart';
+import 'package:analysis_server/src/services/correction/dart/create_parameter.dart';
 import 'package:analysis_server/src/services/correction/dart/create_setter.dart';
 import 'package:analysis_server/src/services/correction/dart/data_driven.dart';
 import 'package:analysis_server/src/services/correction/dart/extend_class_for_mixin.dart';
@@ -1259,6 +1260,7 @@
     CreateField.new,
     CreateGetter.new,
     CreateLocalVariable.new,
+    CreateParameter.new,
     CreateMethodOrFunction.new,
     CreateMixin.new,
     CreateSetter.new,
diff --git a/pkg/analysis_server/test/src/services/correction/fix/create_parameter_test.dart b/pkg/analysis_server/test/src/services/correction/fix/create_parameter_test.dart
new file mode 100644
index 0000000..5fcfb8d
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/create_parameter_test.dart
@@ -0,0 +1,254 @@
+// Copyright (c) 2024, 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/services/correction/fix.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'fix_processor.dart';
+
+void main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(CreateParameterTest);
+  });
+}
+
+@reflectiveTest
+class CreateParameterTest extends FixProcessorTest {
+  @override
+  FixKind get kind => DartFixKind.CREATE_PARAMETER;
+
+  Future<void> test_dynamic_type() async {
+    await resolveTestCode('''
+int f(
+  int b,
+) {
+  var i = b2;
+  return i;
+}
+''');
+    await assertHasFix('''
+int f(
+  int b,
+  dynamic b2,
+) {
+  var i = b2;
+  return i;
+}
+''');
+  }
+
+  Future<void> test_final_comma() async {
+    await resolveTestCode('''
+int f(
+  int b,
+) {
+  int i = b2;
+  return i;
+}
+''');
+    await assertHasFix('''
+int f(
+  int b,
+  int b2,
+) {
+  int i = b2;
+  return i;
+}
+''');
+  }
+
+  Future<void> test_method_type() async {
+    await resolveTestCode('''
+class A{
+  int f(
+    int b,
+  ) {
+    int i = b2;
+    return i;
+  }
+}
+''');
+    await assertHasFix('''
+class A{
+  int f(
+    int b,
+    int b2,
+  ) {
+    int i = b2;
+    return i;
+  }
+}
+''');
+  }
+
+  Future<void> test_multi() async {
+    await resolveTestCode('''
+int f(int b) {
+  int i = b2;
+  return i;
+}
+''');
+    await assertHasFix('''
+int f(int b, int b2) {
+  int i = b2;
+  return i;
+}
+''');
+  }
+
+  Future<void> test_only() async {
+    await resolveTestCode('''
+int f() {
+  int i = b;
+  return i;
+}
+''');
+    await assertHasFix('''
+int f(int b) {
+  int i = b;
+  return i;
+}
+''');
+  }
+
+  Future<void> test_with_constructor() async {
+    await resolveTestCode('''
+class A {
+  A() {
+    int i = b;
+    g(i);
+  }
+}
+void g(int n) {}
+''');
+    await assertHasFix('''
+class A {
+  A(int b) {
+    int i = b;
+    g(i);
+  }
+}
+void g(int n) {}
+''');
+  }
+
+  Future<void> test_with_local_function() async {
+    await resolveTestCode('''
+void f(int a) {
+  int g(int a){
+    int i = b2;
+    return i;
+  }
+  g(3);
+}
+''');
+    await assertHasFix('''
+void f(int a) {
+  int g(int a, int b2){
+    int i = b2;
+    return i;
+  }
+  g(3);
+}
+''');
+  }
+
+  Future<void> test_with_named() async {
+    await resolveTestCode('''
+int f({int? b}) {
+  int i = b2;
+  return i;
+}
+''');
+    await assertHasFix('''
+int f({int? b, required int b2}) {
+  int i = b2;
+  return i;
+}
+''');
+  }
+
+  Future<void> test_with_named_and_positional() async {
+    await resolveTestCode('''
+int f(int a, {int? b}) {
+  int i = b2;
+  return i;
+}
+''');
+    await assertHasFix('''
+int f(int a, int b2, {int? b}) {
+  int i = b2;
+  return i;
+}
+''');
+  }
+
+  Future<void> test_with_named_nullable() async {
+    await resolveTestCode('''
+int? f({int? b}) {
+  int? i = b2;
+  return i;
+}
+''');
+    await assertHasFix('''
+int? f({int? b, int? b2}) {
+  int? i = b2;
+  return i;
+}
+''');
+  }
+
+  Future<void> test_with_optional_positional() async {
+    await resolveTestCode('''
+int f([int? b]) {
+  int i = b2;
+  return i;
+}
+''');
+    await assertHasFix('''
+int f(int b2, [int? b]) {
+  int i = b2;
+  return i;
+}
+''');
+  }
+
+  Future<void> test_with_required_positional_and_optional() async {
+    await resolveTestCode('''
+int f(int a, [int? b]) {
+  int i = b2;
+  return i;
+}
+''');
+    await assertHasFix('''
+int f(int a, int b2, [int? b]) {
+  int i = b2;
+  return i;
+}
+''');
+  }
+
+  Future<void> test_with_required_positional_and_optional_trailing() async {
+    await resolveTestCode('''
+int f(
+  int a, [
+  int? b,
+]) {
+  int i = b2;
+  return i;
+}
+''');
+    await assertHasFix('''
+int f(
+  int a,
+  int b2, [
+  int? b,
+]) {
+  int i = b2;
+  return i;
+}
+''');
+  }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
index 1e3ea49..595e1ad 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
@@ -117,6 +117,7 @@
 import 'create_missing_overrides_test.dart' as create_missing_overrides;
 import 'create_mixin_test.dart' as create_mixin;
 import 'create_no_such_method_test.dart' as create_no_such_method;
+import 'create_parameter_test.dart' as create_parameter;
 import 'create_setter_test.dart' as create_setter;
 import 'data_driven/test_all.dart' as data_driven;
 import 'directives_ordering_test.dart' as directives_ordering;
@@ -381,6 +382,7 @@
     create_function.main();
     create_getter.main();
     create_local_variable.main();
+    create_parameter.main();
     create_method.main();
     create_missing_overrides.main();
     create_mixin.main();